From 9cd83fb4453c61843afdc845215bb092a7c93513 Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Wed, 30 Nov 2022 15:35:08 +0000 Subject: [PATCH 01/48] Unity 2023.1.0a21 C# reference source code --- .../PlayerBuildProgramLibrary.Data/Data.cs | 2 + Editor/Mono/AssemblyHelper.cs | 232 +-- .../AssetDatabase/AssetDatabaseSearching.cs | 2 +- .../SpeedTreeImporter.bindings.cs | 2 + .../AssetPipeline/TextureImporter.bindings.cs | 1 - Editor/Mono/BuildPipeline.bindings.cs | 1 + .../DesktopStandaloneBuildWindowExtension.cs | 83 +- .../BuildPipeline/PostprocessBuildPlayer.cs | 5 +- Editor/Mono/Commands/CommandService.cs | 10 +- Editor/Mono/ConsoleWindow.cs | 5 +- Editor/Mono/ContainerWindow.cs | 8 +- Editor/Mono/DataMode.cs | 296 ++-- Editor/Mono/Delayer.cs | 26 +- Editor/Mono/EditorApplication.cs | 17 + Editor/Mono/EditorGUI.cs | 103 +- Editor/Mono/EditorGUIUtility.cs | 2 +- Editor/Mono/EditorTextSettings.cs | 75 + .../Mono/EditorUserBuildSettings.bindings.cs | 42 +- Editor/Mono/EditorUserBuildSettingsUtils.cs | 2 +- Editor/Mono/EditorWindow.cs | 6 + Editor/Mono/GI/InputExtraction.bindings.cs | 254 +++ Editor/Mono/GI/LightBaker.bindings.cs | 564 +++++++ Editor/Mono/GI/Lightmapping.bindings.cs | 101 +- Editor/Mono/GUI/DockArea.cs | 14 +- Editor/Mono/GUI/LazyLoadReferenceField.cs | 2 +- .../Mono/GUI/Toolbars/Core/EditorToolbar.cs | 1 + Editor/Mono/GUI/Tools/BuiltinTools.cs | 2 +- Editor/Mono/GUI/Tools/EditorToolGUI.cs | 36 +- Editor/Mono/GUI/Tools/EditorToolUtility.cs | 16 +- .../Mono/GUI/Tools/GameObjectToolContext.cs | 2 +- Editor/Mono/GUI/WindowLayout.cs | 166 +- Editor/Mono/GUIView.cs | 12 + Editor/Mono/HostView.cs | 179 +-- .../DesktopPluginImporterExtension.cs | 19 +- .../TextureImporterInspector.cs | 34 +- Editor/Mono/Inspector/AudioSourceInspector.cs | 2 +- Editor/Mono/Inspector/CameraEditorUtils.cs | 2 +- Editor/Mono/Inspector/CameraOverlay.cs | 2 +- Editor/Mono/Inspector/Editor.cs | 14 +- .../Mono/Inspector/EditorSettingsInspector.cs | 4 +- .../Inspector/Enlighten/LightmapParameters.cs | 40 +- Editor/Mono/Inspector/GenericInspector.cs | 10 +- .../Inspector/GraphicsSettingsInspector.cs | 18 + Editor/Mono/Inspector/InspectorWindow.cs | 30 +- .../Inspector/LightProbeGroupInspector.cs | 2 +- .../Mono/Inspector/LightingSettingsEditor.cs | 196 +-- Editor/Mono/Inspector/MaterialEditor.cs | 34 +- .../Inspector/MinMaxCurvePropertyDrawer.cs | 4 +- .../Inspector/MinMaxGradientPropertyDrawer.cs | 2 +- .../NotSupportedOnRenderPipelineInspector.cs | 5 + .../PlayerSettingsEditor.cs | 13 + Editor/Mono/Inspector/PropertyEditor.cs | 151 +- .../Mono/Inspector/QualitySettingsEditor.cs | 10 +- .../Mono/Inspector/ReflectionProbeEditor.cs | 2 +- .../Inspector/RenderPipelineEditorUtility.cs | 29 +- .../Inspector/RendererLightingSettings.cs | 18 - ...erPipelineExtensionAttribute.deprecated.cs | 26 + Editor/Mono/Inspector/Texture3DPreview.cs | 20 + Editor/Mono/Inspector/UnityEventDrawer.cs | 2 +- Editor/Mono/Modules/BeeBuildPostprocessor.cs | 99 +- Editor/Mono/Overlays/OverlayAttribute.cs | 2 +- Editor/Mono/Overlays/OverlayResizer.cs | 27 + Editor/Mono/Overlays/OverlayUtilities.cs | 15 +- .../FrameDebuggerEventDisplayData.cs | 43 +- .../PerformanceTools/FrameDebuggerHelper.cs | 2 +- .../PerformanceTools/FrameDebuggerStyles.cs | 2 +- Editor/Mono/PlayerSettings.bindings.cs | 10 +- Editor/Mono/PlayerSettingsAndroid.bindings.cs | 45 + Editor/Mono/PlayerSettingsSwitch.bindings.cs | 34 +- Editor/Mono/Prefabs/PrefabUtility.cs | 2 +- .../PreferencesSettingsProviders.cs | 8 + .../PrefabStage/PrefabStageUtility.cs | 5 +- ...ngExplorerExtensionAttribute.deprecated.cs | 17 + .../LightingExplorerWindow.cs | 30 +- .../Mono/SceneModeWindows/LightingWindow.cs | 228 +-- .../LightingWindowEnvironmentTab.cs | 9 +- .../LightingWindowLightingTab.cs | 34 +- .../LightingWindowLightmapPreviewTab.cs | 150 -- Editor/Mono/SceneView/SceneView.cs | 6 +- .../Implementations/ExposedReferenceDrawer.cs | 2 +- .../Implementations/PropertyDrawers.cs | 94 +- .../Mono/ScriptAttributeGUI/PropertyDrawer.cs | 4 + .../ScriptAttributeGUI/PropertyHandler.cs | 6 +- .../ScriptAttributeUtility.cs | 8 +- Editor/Mono/ScriptReloadProperties.cs | 8 +- Editor/Mono/SerializedObject.bindings.cs | 6 + .../SettingsWindow/GraphicsSettingsEditors.cs | 2 + Editor/Mono/Tutorial/Highlighter.bindings.cs | 4 +- Editor/Mono/Tutorial/Highlighter.cs | 26 +- Editor/Mono/UIElements/Controls/ColorField.cs | 19 + Editor/Mono/UIElements/Controls/CurveField.cs | 6 + .../Mono/UIElements/Controls/GradientField.cs | 36 +- Editor/Mono/UIElements/Controls/MaskField.cs | 8 +- .../Mono/UIElements/Controls/ObjectField.cs | 21 +- .../Mono/UIElements/Controls/PropertyField.cs | 6 +- .../Controls/Toolbar/SearchFieldBase.cs | 13 +- .../Controls/Toolbar/ToolbarMenu.cs | 10 + .../Toolbar/ToolbarPopupSearchField.cs | 4 + .../Controls/Toolbar/ToolbarSearchField.cs | 20 - .../Controls/Toolbar/ToolbarSpacer.cs | 5 + .../UIElements/Inspector/InspectorElement.cs | 11 +- .../Editor/Builder/Builder.cs | 10 + .../Document/BuilderDocumentOpenUSS.cs | 26 + .../Document/BuilderDocumentOpenUXML.cs | 12 +- .../Builder/Explorer/BuilderExplorer.cs | 8 +- .../Inspector/BuilderInspectorAttributes.cs | 5 + .../BuilderInspectorInheritedStyles.cs | 9 +- .../Builder/Library/BuilderLibraryContent.cs | 56 +- .../Library/BuilderLibraryProjectScanner.cs | 2 - .../Utilities/BuilderAnalyticsUtility.cs | 188 +++ .../BuilderAssetModificationProcessor.cs | 13 - .../Utilities/BuilderAssetUtilities.cs | 29 +- .../BuilderInPlaceTextEditingUtilities.cs | 3 +- .../Utilities/PercentSlider/PercentSlider.cs | 56 +- .../VisualTreeAssetUtilities.cs | 69 + .../com.unity.ui/Core/ClampedDragger.cs | 3 + .../Core/Controls/BaseBoolField.cs | 9 + .../Core/Controls/BaseListView.cs | 77 +- .../Core/Controls/BasePopupField.cs | 8 +- .../com.unity.ui/Core/Controls/BaseSlider.cs | 46 +- .../Core/Controls/BaseTreeView.cs | 13 +- .../Controls/BaseVerticalCollectionView.cs | 106 +- .../Core/Controls/DropdownField.cs | 2 +- .../com.unity.ui/Core/Controls/EnumField.cs | 4 + .../com.unity.ui/Core/Controls/Foldout.cs | 17 +- .../Core/Controls/GenericDropdownMenu.cs | 1 + .../com.unity.ui/Core/Controls/GroupBox.cs | 9 + .../com.unity.ui/Core/Controls/HelpBox.cs | 17 +- .../com.unity.ui/Core/Controls/Image.cs | 110 +- .../Core/Controls/InputField/BaseField.cs | 36 +- .../Controls/InputField/KeyboardTextEditor.cs | 8 +- .../Core/Controls/InputField/TextField.cs | 13 +- .../Controls/InputField/TextInputFieldBase.cs | 156 +- .../com.unity.ui/Core/Controls/ListView.cs | 39 +- .../Core/Controls/MinMaxSlider.cs | 22 + .../com.unity.ui/Core/Controls/PopupField.cs | 10 +- .../com.unity.ui/Core/Controls/ProgressBar.cs | 29 +- .../Core/Controls/RadioButtonGroup.cs | 7 +- .../com.unity.ui/Core/Controls/ScrollView.cs | 71 +- .../com.unity.ui/Core/Controls/Scroller.cs | 47 +- .../Core/Controls/TextValueField.cs | 14 +- .../com.unity.ui/Core/Controls/TreeView.cs | 46 +- .../Core/Controls/TwoPaneSplitView.cs | 13 +- .../Core/Conversions/UIConversion.cs | 10 + .../com.unity.ui/Core/Events/EventHandler.cs | 9 + .../Core/Events/KeyboardEvents.cs | 1 + .../com.unity.ui/Core/FocusController.cs | 45 +- .../com.unity.ui/Core/IMGUIContainer.cs | 31 +- .../com.unity.ui/Core/ImmediateModeElement.cs | 13 +- ModuleOverrides/com.unity.ui/Core/Panel.cs | 2 + .../Core/Renderer/UIRCommandManipulator.cs | 176 ++- .../Core/Renderer/UIRElementBuilder.cs | 2 + .../Core/Renderer/UIREntryProcessor.cs | 2 + .../Core/Renderer/UIREntryRecorder.cs | 4 +- .../Core/Renderer/UIRLayoutUpdater.cs | 96 +- .../Core/Renderer/UIRMeshGenerator.cs | 115 +- .../Core/Renderer/UIRPainter2D.cs | 18 +- .../Core/Renderer/UIRRenderChain.cs | 29 +- .../Core/Renderer/UIRRenderEvents.cs | 16 +- .../Core/Renderer/UIRRepaintUpdater.cs | 7 +- .../Core/Renderer/UIRTextureSlotManager.cs | 12 +- .../Core/Renderer/UIRVEShaderInfoAllocator.cs | 105 +- .../Renderer/UIRVisualChangesProcessor.cs | 39 +- .../Renderer/UIRenderer/UIRenderDevice.cs | 117 +- .../Core/Renderer/UIRenderer/UIRenderers.cs | 2 + .../Core/Style/Generated/ComputedStyle.cs | 2 +- .../Core/Style/Generated/InlineStyleAccess.cs | 2 +- .../Generated/ResolvedStyleProperties.cs | 97 ++ .../Core/Style/Generated/StyleProperties.cs | 101 ++ .../com.unity.ui/Core/TemplateContainer.cs | 6 + .../Core/Text/PanelTextSettings.cs | 37 +- .../com.unity.ui/Core/Text/UITKTextHandle.cs | 11 +- .../com.unity.ui/Core/TextElement.cs | 28 +- .../com.unity.ui/Core/TextElementEdition.cs | 83 +- .../com.unity.ui/Core/TextElementSelection.cs | 184 ++- .../com.unity.ui/Core/UXML/UxmlUtility.cs | 33 + .../com.unity.ui/Core/VisualElement.cs | 79 +- .../Core/VisualElementBindableProperties.cs | 28 + .../Core/VisualElementDataBinding.cs | 90 ++ .../Core/VisualElementFocusRing.cs | 4 +- .../Core/VisualElementHierarchy.cs | 18 + .../Core/VisualElementStyleAccess.cs | 1 + .../com.unity.ui/Core/VisualElementTooltip.cs | 6 + .../Core/VisualTreeViewDataUpdater.cs | 11 +- .../Editor/Bindings/BindingExtensions.cs | 37 +- .../Editor/Bindings/ListViewBindings.cs | 19 +- .../Editor/Debugger/DebuggerSearchBar.cs | 2 +- .../Editor/Debugger/DebuggerTreeView.cs | 4 + .../Editor/Debugger/StylesDebugger.cs | 22 + .../Delegates/EditorDelegateRegistration.cs | 5 +- .../GameObjects/UIDocumentHierarchyWatcher.cs | 2 +- .../AndroidJNI/AndroidAssetPacks.bindings.cs | 2 + .../ScriptBindings/AssetOrigin.binding.cs | 1 + .../Public/VideoImporter.bindings.cs | 8 +- .../Managed/BuildReport.bindings.cs | 2 + .../Managed/BuildSummary.cs | 1 + .../Controls/EditorToolbarIcon.cs | 88 ++ .../Controls/EditorToolbarToggle.cs | 129 +- Modules/EditorToolbar/Controls/ToolButton.cs | 10 +- .../Controls/ToolContextButton.cs | 2 + .../ToolbarElements/EditorToolsToolbar.cs | 59 +- Modules/IMGUI/AssemblyInfo.cs | 1 + Modules/IMGUI/DrawStates.cs | 12 +- Modules/IMGUI/GUI.cs | 12 +- Modules/IMGUI/GUIContent.cs | 35 +- Modules/IMGUI/GUILayout.cs | 8 +- Modules/IMGUI/GUIStyle.bindings.cs | 35 +- Modules/IMGUI/GUIStyle.cs | 82 +- Modules/IMGUI/GUIUtility.cs | 5 +- Modules/IMGUI/IMGUITextHandle.cs | 166 ++ Modules/IMGUI/RuntimeTextSettings.cs | 31 + Modules/IMGUI/TextEditingUtilities.cs | 100 +- Modules/IMGUI/TextEditor.cs | 1397 +++-------------- Modules/IMGUI/TextSelectingUtilities.cs | 93 +- .../Editor/Managed/RequestProgress.cs | 16 +- .../Editor/Services/AssetStore/Asset.cs | 17 + .../AssetStore/AssetSelectionHandler.cs | 40 + .../Services/AssetStore/AssetStoreCache.cs | 90 ++ .../Services/AssetStore/AssetStoreClientV2.cs | 33 +- .../AssetStore/AssetStoreImportedPackage.cs | 69 + .../AssetStore/AssetStoreListOperation.cs | 12 +- .../AssetStore/AssetStoreLocalInfo.cs | 2 + .../AssetStore/AssetStorePackageFactory.cs | 13 +- .../AssetStore/AssetStorePackageInstaller.cs | 141 ++ .../AssetStore/AssetStorePackageVersion.cs | 8 +- .../AssetStore/AssetStoreVersionList.cs | 21 +- .../Services/Common/BasePackageVersion.cs | 2 + .../Editor/Services/Common/Package.cs | 3 + .../Editor/Services/Common/ResourceLoader.cs | 24 +- .../Editor/Services/Interfaces/IOperation.cs | 1 + .../Services/Interfaces/IPackageVersion.cs | 2 + .../Packages/PackageOperationDispatcher.cs | 24 +- .../Editor/Services/Packages/PackageState.cs | 1 + .../Services/Packages/RefreshOptions.cs | 3 +- .../Services/Proxies/AssetDatabaseProxy.cs | 45 +- .../Editor/Services/ServicesContainer.cs | 28 +- .../Editor/Services/Upm/UpmBaseOperation.cs | 1 + .../Editor/Services/Upm/UpmClient.cs | 10 +- .../Editor/Services/Upm/UpmSearchOperation.cs | 11 +- .../Editor/UI/Common/PageFilters.cs | 2 + .../Editor/UI/Common/PageRefreshHandler.cs | 61 +- .../UI/Filters/AssetStoreFiltersWindow.cs | 1 + .../UI/MultiSelect/MultiSelectDetails.cs | 9 +- .../UI/MultiSelect/RemoveFoldoutGroup.cs | 4 +- .../MultiSelect/RemoveImportedFoldoutGroup.cs | 28 + .../Editor/UI/PackageDetails.cs | 5 + .../Editor/UI/PackageDetailsBody.cs | 1 + .../PackageDetailsImportedAssetsTab.cs | 194 +++ .../UI/PackageDetailsTabs/SortedColumn.cs | 29 + .../PackageManagerUI/Editor/UI/PackageItem.cs | 1 + .../Editor/UI/PackageManagerPrefs.cs | 13 +- .../Editor/UI/PackageManagerToolbar.cs | 6 + .../UI/SelectionWindow/SelectionWindow.cs | 64 + .../UI/SelectionWindow/SelectionWindowData.cs | 193 +++ .../SelectionWindow/SelectionWindowFooter.cs | 59 + .../SelectionWindow/SelectionWindowHeader.cs | 48 + .../SelectionWindow/SelectionWindowProxy.cs | 35 + .../UI/SelectionWindow/SelectionWindowRoot.cs | 136 ++ .../UI/SelectionWindow/SelectionWindowRow.cs | 95 ++ .../SelectionWindowTreeView.cs | 71 + .../UI/ToolBar/PackageRemoveImportedButton.cs | 58 + .../Editor/UI/ToolBar/PackageToolBar.cs | 7 + .../ScriptBindings/ParticleSystem.bindings.cs | 2 +- .../ScriptBindings/Dynamics.bindings.cs | 7 + .../ImmediatePhysics.bindings.cs | 80 + .../PhysicsGeometry.bindings.cs | 171 ++ .../Managed/Colliders/EditableLineHandle2D.cs | 6 +- .../Managed/Colliders/EditablePath2D.cs | 6 +- .../Colliders/PolygonCollider2DTool.cs | 2 +- .../Settings/Physics2DSettingsEditor.cs | 4 +- Modules/PresetsUIEditor/PresetEditor.cs | 2 +- .../ProfilerEditor/ProfilerWindow/Chart.cs | 62 +- .../ProfilerWindow/ProfilerWindow.cs | 2 +- Modules/Properties/Runtime/Attributes.cs | 9 +- .../Properties/Runtime/Properties/Property.cs | 2 +- .../Runtime/Properties/PropertyPath.cs | 14 +- .../Properties/ReflectedMemberProperty.cs | 11 +- .../Properties/Runtime/Utility/TypeUtility.cs | 22 +- .../Module/PropertiesEditorModule.cs | 21 - Modules/QuickSearch/Editor/ISearchView.cs | 1 + .../PropertyDatabase/PropertyDatabase.cs | 56 +- .../Editor/Providers/AssetProvider.cs | 17 +- .../Editor/Providers/FindProvider.cs | 52 +- .../Editor/Providers/SceneProvider.cs | 4 +- .../Editor/Providers/SceneQueryEngine.cs | 2 +- .../Editor/QueryBuilder/QueryBuilder.cs | 18 +- .../QueryBuilder/QueryBuilderHelpers.cs | 4 +- .../Editor/QueryBuilder/QueryMarker.cs | 19 +- .../SearchExpression/SearchExpression.cs | 2 + .../SearchExpression/TaskEvaluatorManager.cs | 4 +- Modules/QuickSearch/Editor/SearchMonitor.cs | 14 +- .../Editor/SearchPreviewManager.cs | 2 +- .../Editor/SearchQuery/SearchQueryEditor.cs | 11 +- Modules/QuickSearch/Editor/SearchService.cs | 48 +- Modules/QuickSearch/Editor/SearchSettings.cs | 45 +- Modules/QuickSearch/Editor/SearchUtils.cs | 25 +- .../Editor/Selectors/MaterialSelectors.cs | 2 +- Modules/QuickSearch/Editor/UI/IResultView.cs | 2 + .../Editor/UI/SearchContextPropertyDrawer.cs | 2 +- .../Editor/UI/SearchPickerWindow.cs | 7 +- .../QuickSearch/Editor/UI/SearchViewState.cs | 1 + Modules/QuickSearch/Editor/UI/UndoManager.cs | 41 +- Modules/QuickSearch/Editor/UITK/GridView.cs | 54 +- .../Editor/UITK/SearchBaseCollectionView.cs | 5 + .../Editor/UITK/SearchDetailView.cs | 4 +- .../QuickSearch/Editor/UITK/SearchElement.cs | 25 + .../Editor/UITK/SearchEmptyView.cs | 84 +- .../UITK/SearchGlobalEventHandlerManager.cs | 12 + .../QuickSearch/Editor/UITK/SearchGridView.cs | 9 + .../Editor/UITK/SearchItemEditorElement.cs | 6 +- .../Editor/UITK/SearchQueryBuilderView.cs | 5 +- .../Editor/UITK/SearchQueryListView.cs | 2 +- .../Editor/UITK/SearchTableViewCell.cs | 8 +- .../QuickSearch/Editor/UITK/SearchToolbar.cs | 15 +- Modules/QuickSearch/Editor/UITK/SearchView.cs | 185 ++- .../QuickSearch/Editor/UITK/SearchViewItem.cs | 7 +- .../QuickSearch/Editor/UITK/SearchWindow.cs | 132 +- Modules/QuickSearch/Editor/Utilities/Utils.cs | 38 +- .../BlockLinkOverride.binding.cs | 24 +- .../CustomizationPointImplementation.cs | 1 - .../ScriptBindings/DataType.binding.cs | 1 - .../KeywordDescriptor.binding.cs | 10 +- .../ScriptBindings/PassIdentifier.cs | 21 - .../ScriptBindings/PassStageType.binding.cs | 1 - .../ShaderAttribute.bindings.cs | 48 +- .../ShaderContainer.bindings.cs | 6 - .../StageDescription.binding.cs | 1 - .../ScriptBindings/TemplatePass.binding.cs | 12 - Modules/StyleSheetsEditor/StylePainter.cs | 2 +- .../TextCoreTextEngine/Managed/FontAsset.cs | 30 +- .../TextCoreTextEngine/Managed/MeshInfo.cs | 409 +++-- .../Managed/TextGenerator.cs | 474 ++++-- .../Managed/TextGeneratorUtilities.cs | 223 ++- .../TextCoreTextEngine/Managed/TextHandle.cs | 137 +- .../TextCoreTextEngine/Managed/TextInfo.cs | 9 +- .../Managed/TextMarkupTagsCommon.cs | 2 +- .../Managed/TextSettings.cs | 27 +- .../Managed/TextShaderUtilities.cs | 21 + .../TextCoreTextEngine/MeshInfo.bindings.cs | 24 + .../TextCoreVertex.bindings.cs | 25 + .../Managed/FontAssetCreationMenu.cs | 4 +- .../EditorAnalytics.bindings.cs | 6 +- .../Public/VisualEffectEditorDefault.cs | 4 +- Projects/CSharp/UnityEditor.csproj | 83 +- Projects/CSharp/UnityEngine.csproj | 42 + README.md | 2 +- .../Application/Application.bindings.cs | 2 + Runtime/Export/BaseClass.cs | 5 +- Runtime/Export/Debug/Debug.bindings.cs | 2 - Runtime/Export/Device/Application.cs | 2 + Runtime/Export/Geometry/RectInt.cs | 30 + Runtime/Export/Graphics/Graphics.bindings.cs | 3 + Runtime/Export/Graphics/GraphicsEnums.cs | 1 + .../Graphics/GraphicsSettings.bindings.cs | 3 + .../Graphics/GraphicsTexture.bindings.cs | 107 ++ Runtime/Export/Graphics/Light.bindings.cs | 1 + ...ayTracingAccelerationStructure.bindings.cs | 2 +- .../RenderingCommandBuffer.bindings.cs | 5 +- .../Export/Graphics/RenderingCommandBuffer.cs | 13 +- .../Graphics/SupportedOnRenderPipeline.cs | 3 + Runtime/Export/Graphics/Texture.bindings.cs | 18 +- Runtime/Export/Graphics/Texture.cs | 84 +- Runtime/Export/Math/Mathf.cs | 1 + .../Export/Scripting/Awaitable.Threading.cs | 74 + Runtime/Export/Scripting/Awaitable.cs | 4 +- .../Scripting/AwaitableCompletionSource.cs | 127 ++ .../Scripting/UnityEngineObject.bindings.cs | 119 +- .../Scripting/UnitySynchronizationContext.cs | 4 +- .../Export/Shaders/ComputeShader.bindings.cs | 2 +- .../Shaders/RayTracingShader.bindings.cs | 4 +- Runtime/Export/Shaders/Shader.bindings.cs | 2 +- .../CombineForStaticBatching.cs | 2 +- .../Export/StaticShim/ApplicationShimBase.cs | 12 +- .../Unity.CompilationPipeline.Common.dll | Bin 374 files changed, 10489 insertions(+), 4428 deletions(-) create mode 100644 Editor/Mono/EditorTextSettings.cs create mode 100644 Editor/Mono/GI/InputExtraction.bindings.cs create mode 100644 Editor/Mono/GI/LightBaker.bindings.cs create mode 100644 Editor/Mono/Inspector/ScriptableRenderPipelineExtensionAttribute.deprecated.cs create mode 100644 Editor/Mono/SceneModeWindows/LightingExplorerExtensionAttribute.deprecated.cs create mode 100644 External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAnalyticsUtility.cs create mode 100644 ModuleOverrides/com.unity.ui/Core/Style/Generated/ResolvedStyleProperties.cs create mode 100644 ModuleOverrides/com.unity.ui/Core/Style/Generated/StyleProperties.cs create mode 100644 ModuleOverrides/com.unity.ui/Core/UXML/UxmlUtility.cs create mode 100644 ModuleOverrides/com.unity.ui/Core/VisualElementBindableProperties.cs create mode 100644 ModuleOverrides/com.unity.ui/Core/VisualElementDataBinding.cs create mode 100644 Modules/EditorToolbar/Controls/EditorToolbarIcon.cs create mode 100644 Modules/IMGUI/IMGUITextHandle.cs create mode 100644 Modules/IMGUI/RuntimeTextSettings.cs create mode 100644 Modules/PackageManagerUI/Editor/Services/AssetStore/Asset.cs create mode 100644 Modules/PackageManagerUI/Editor/Services/AssetStore/AssetSelectionHandler.cs create mode 100644 Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreImportedPackage.cs create mode 100644 Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageInstaller.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveImportedFoldoutGroup.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsImportedAssetsTab.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/SortedColumn.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindow.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowData.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowFooter.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowHeader.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowProxy.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowRoot.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowRow.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/SelectionWindow/SelectionWindowTreeView.cs create mode 100644 Modules/PackageManagerUI/Editor/UI/ToolBar/PackageRemoveImportedButton.cs create mode 100644 Modules/Physics/ScriptBindings/ImmediatePhysics.bindings.cs create mode 100644 Modules/Physics/ScriptBindings/PhysicsGeometry.bindings.cs rename Editor/Mono/Inspector/LineHandle.cs => Modules/Physics2DEditor/Managed/Colliders/EditableLineHandle2D.cs (97%) delete mode 100644 Modules/PropertiesEditor/Module/PropertiesEditorModule.cs delete mode 100644 Modules/ShaderFoundry/ScriptBindings/PassIdentifier.cs create mode 100644 Modules/TextCoreTextEngine/MeshInfo.bindings.cs create mode 100644 Modules/TextCoreTextEngine/TextCoreVertex.bindings.cs create mode 100644 Runtime/Export/Graphics/GraphicsTexture.bindings.cs create mode 100644 Runtime/Export/Scripting/Awaitable.Threading.cs create mode 100644 Runtime/Export/Scripting/AwaitableCompletionSource.cs rename artifacts/Stevedore/{unity-compiler-linux-x64_f3f3 => unity-compiler-linux-x64_1341}/Unity.CompilationPipeline.Common/Unity.CompilationPipeline.Common.dll (100%) diff --git a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs index 4996a3ff9..babe12bcf 100644 --- a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs @@ -47,6 +47,7 @@ public class PlayerBuildConfig public string ApplicationIdentifier; public string Architecture; public ScriptingBackend ScriptingBackend; + public bool NoGUID; public bool InstallIntoBuildsFolder; public bool GenerateIdeProject; public bool Development; @@ -60,6 +61,7 @@ public class PlayerBuildConfig public class BuiltFilesOutput { public string[] Files = new string[0]; + public string BootConfigArtifact; } public class LinkerConfig diff --git a/Editor/Mono/AssemblyHelper.cs b/Editor/Mono/AssemblyHelper.cs index 73a0f4aca..afd649332 100644 --- a/Editor/Mono/AssemblyHelper.cs +++ b/Editor/Mono/AssemblyHelper.cs @@ -7,14 +7,11 @@ using System.Linq; using System.Reflection; using System.Collections.Generic; -using System.Diagnostics; using Mono.Cecil; -using UnityEditor.Build; using UnityEditor.Modules; using UnityEditorInternal; using UnityEngine; using System.Runtime.InteropServices; -using UnityEditor.VisualStudioIntegration; using UnityEngine.Scripting; using Debug = UnityEngine.Debug; using Unity.Profiling; @@ -24,181 +21,11 @@ namespace UnityEditor { - internal partial class AssemblyHelper + internal class AssemblyHelper { static Dictionary managedToDllType = new Dictionary(); static BuildPlayerDataExtractor m_BuildPlayerDataExtractor = new BuildPlayerDataExtractor(); - static public string[] GetNamesOfAssembliesLoadedInCurrentDomain() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var locations = new List(); - foreach (var a in assemblies) - { - try - { - locations.Add(a.Location); - } - catch (NotSupportedException) - { - //we have some "dynamic" assmeblies that do not have a filename - } - } - return locations.ToArray(); - } - - static public string ExtractInternalAssemblyName(string path) - { - try - { - AssemblyDefinition definition = AssemblyDefinition.ReadAssembly(path); - return definition.Name.Name; - } - catch - { - return ""; - } - } - - static AssemblyDefinition GetAssemblyDefinitionCached(string path, Dictionary cache) - { - if (cache.ContainsKey(path)) - return cache[path]; - - AssemblyDefinition definition = AssemblyDefinition.ReadAssembly(path); - cache[path] = definition; - return definition; - } - - static private bool CouldBelongToDotNetOrWindowsRuntime(string assemblyPath) - { - return assemblyPath.IndexOf("mscorlib.dll") != -1 || - assemblyPath.IndexOf("System.") != -1 || - assemblyPath.IndexOf("Microsoft.") != -1 || - assemblyPath.IndexOf("Windows.") != -1 || - assemblyPath.IndexOf("WinRTLegacy.dll") != -1 || - assemblyPath.IndexOf("platform.dll") != -1; - } - - static private bool IgnoreAssembly(string assemblyPath, BuildTarget target, ScriptingImplementation scriptingImplementation) - { -#pragma warning disable 618 - if (target == BuildTarget.WSAPlayer || scriptingImplementation == ScriptingImplementation.CoreCLR) - { - if (CouldBelongToDotNetOrWindowsRuntime(assemblyPath)) - return true; - } - else if (target == BuildTarget.XboxOne) - { - var profile = PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.XboxOne); - if (profile == ApiCompatibilityLevel.NET_4_6 || profile == ApiCompatibilityLevel.NET_Standard_2_0) - { - if (CouldBelongToDotNetOrWindowsRuntime(assemblyPath)) - return true; - } - } - - return IsInternalAssembly(assemblyPath); - } - - static private void AddReferencedAssembliesRecurse(string assemblyPath, List alreadyFoundAssemblies, string[] allAssemblyPaths, string[] foldersToSearch, Dictionary cache, BuildTarget target, ScriptingImplementation scriptingImplementation) - { - if (IgnoreAssembly(assemblyPath, target, scriptingImplementation)) - return; - - if (!File.Exists(assemblyPath)) - return; - - AssemblyDefinition assembly = GetAssemblyDefinitionCached(assemblyPath, cache); - if (assembly == null) - throw new System.ArgumentException("Referenced Assembly " + Path.GetFileName(assemblyPath) + " could not be found!"); - - // Ignore it if we already added the assembly - if (alreadyFoundAssemblies.IndexOf(assemblyPath) != -1) - return; - - alreadyFoundAssemblies.Add(assemblyPath); - - var architectureSpecificPlugins = PluginImporter.GetImporters(target).Where(i => - { - var cpu = i.GetPlatformData(target, "CPU"); - return !string.IsNullOrEmpty(cpu) && !string.Equals(cpu, "AnyCPU", StringComparison.InvariantCultureIgnoreCase); - }).Select(i => Path.GetFileName(i.assetPath)).Distinct(); - - // Go through all referenced assemblies - foreach (AssemblyNameReference referencedAssembly in assembly.MainModule.AssemblyReferences) - { - // Special cases for Metro - if (referencedAssembly.Name == "BridgeInterface") continue; - if (referencedAssembly.Name == "WinRTBridge") continue; - if (referencedAssembly.Name == "UnityEngineProxy") continue; - if (IgnoreAssembly(referencedAssembly.Name + ".dll", target, scriptingImplementation)) continue; - - string foundPath = FindAssemblyName(referencedAssembly.FullName, referencedAssembly.Name, allAssemblyPaths, foldersToSearch, cache); - - if (foundPath == "") - { - // Ignore architecture specific plugin references - var found = false; - foreach (var extension in new[] { ".dll", ".winmd" }) - { - if (architectureSpecificPlugins.Any(p => string.Equals(p, referencedAssembly.Name + extension, StringComparison.InvariantCultureIgnoreCase))) - { - found = true; - break; - } - } - if (found) - continue; - throw new System.ArgumentException(string.Format("The Assembly {0} is referenced by {1} ('{2}'). But the dll is not allowed to be included or could not be found.", - referencedAssembly.Name, - assembly.MainModule.Assembly.Name.Name, - assemblyPath)); - } - - AddReferencedAssembliesRecurse(foundPath, alreadyFoundAssemblies, allAssemblyPaths, foldersToSearch, cache, target, scriptingImplementation); - } - } - - static string FindAssemblyName(string fullName, string name, string[] allAssemblyPaths, string[] foldersToSearch, Dictionary cache) - { - // Search in provided assemblies - for (int i = 0; i < allAssemblyPaths.Length; i++) - { - if (!File.Exists(allAssemblyPaths[i])) - continue; - - AssemblyDefinition definition = GetAssemblyDefinitionCached(allAssemblyPaths[i], cache); - if (definition.MainModule.Assembly.Name.Name == name) - return allAssemblyPaths[i]; - } - - // Search in GAC - foreach (string folder in foldersToSearch) - { - string pathInGacFolder = Path.Combine(folder, name + ".dll"); - if (File.Exists(pathInGacFolder)) - return pathInGacFolder; - } - return ""; - } - - [RequiredByNativeCode] - static public string[] FindAssembliesReferencedBy(string[] paths, string[] foldersToSearch, BuildTarget target, ScriptingImplementation scriptingImplementation) - { - List unique = new List(); - string[] allAssemblyPaths = paths; - - var cache = new Dictionary(); - for (int i = 0; i < paths.Length; i++) - AddReferencedAssembliesRecurse(paths[i], unique, allAssemblyPaths, foldersToSearch, cache, target, scriptingImplementation); - - for (int i = 0; i < paths.Length; i++) - unique.Remove(paths[i]); - - return unique.ToArray(); - } - static public bool IsUnityEngineModule(AssemblyDefinition assembly) { return assembly.CustomAttributes.Any(a => a.AttributeType.FullName == typeof(UnityEngineModuleAssembly).FullName); @@ -209,33 +36,6 @@ static public bool IsUnityEngineModule(Assembly assembly) return assembly.GetCustomAttributes(typeof(UnityEngineModuleAssembly), false).Length > 0; } - private static bool IsTypeAUserExtendedScript(TypeReference type) - { - if (type == null || type.FullName == "System.Object") - return false; - - try - { - var typeDefinition = type.Resolve(); - var attributes = typeDefinition.CustomAttributes; - for (var i = 0; i < attributes.Count; i++) - { - if (attributes[i].Constructor.DeclaringType.FullName == "UnityEngine.ExtensionOfNativeClassAttribute") - return true; - } - - if (typeDefinition.BaseType != null) - return IsTypeAUserExtendedScript(typeDefinition.BaseType); - } - catch (AssemblyResolutionException) - { - // just eat exception if we fail to load assembly here. - // failure should be handled better in other places. - } - - return false; - } - public static string[] GetDefaultAssemblySearchPaths() { // Add the path to all available precompiled assemblies @@ -359,36 +159,6 @@ public static bool IsInternalAssembly(string file) return ModuleUtils.GetAdditionalReferencesForUserScripts().Any(p => p.Equals(file)); } - const int kDefaultDepth = 10; - internal static ICollection FindAssemblies(string basePath) - { - return FindAssemblies(basePath, kDefaultDepth); - } - - internal static ICollection FindAssemblies(string basePath, int maxDepth) - { - var assemblies = new List(); - - if (0 == maxDepth) - return assemblies; - - try - { - DirectoryInfo directory = new DirectoryInfo(basePath); - assemblies.AddRange(directory.GetFiles() - .Where(file => IsManagedAssembly(file.FullName)) - .Select(file => file.FullName)); - foreach (DirectoryInfo subdirectory in directory.GetDirectories()) - assemblies.AddRange(FindAssemblies(subdirectory.FullName, maxDepth - 1)); - } - catch (Exception) - { - // Return what we have now - } - - return assemblies; - } - /// /// Performs a depth-first-search topological sort on the input assemblies, /// based on the outgoing assembly references from each assembly. The diff --git a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs index c3bd1ded7..fedd4d13e 100644 --- a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs +++ b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs @@ -88,7 +88,7 @@ private static IEnumerator FindInFolders(SearchFilter searchFilter, Func subAsse texImporter.sRGBTexture = false; // extra texture does not contain color data, hence shouldn't be sRGB. texImporter.SaveAndReimport(); } + + } } } diff --git a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs index 09252985c..77c87e4ad 100644 --- a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs @@ -468,7 +468,6 @@ public void SetTextureSettings(TextureImporterSettings src) internal extern bool removeMatte { get; set; } public extern bool ignorePngGamma { get; set; } - internal static readonly int MaxTextureSizeAllowedForReadable = 8192; //keep in sync with TextureImporter.h // This is for remapping Sprite that are renamed. extern internal bool GetNameFromInternalIDMap(long id, ref string name); diff --git a/Editor/Mono/BuildPipeline.bindings.cs b/Editor/Mono/BuildPipeline.bindings.cs index 9d3bc9cd6..c427acb8e 100644 --- a/Editor/Mono/BuildPipeline.bindings.cs +++ b/Editor/Mono/BuildPipeline.bindings.cs @@ -738,6 +738,7 @@ private static bool DoesBuildTargetSupportPlayerConnectionPlayerToEditor(BuildTa targetPlatform == BuildTarget.StandaloneWindows || targetPlatform == BuildTarget.StandaloneWindows64 || targetPlatform == BuildTarget.StandaloneLinux64 || + targetPlatform == BuildTarget.iOS || // Android: support connection from player to Editor in both cases // connecting to 127.0.0.1 (when both Editor and Android are on localhost using USB cable) // connecting to , the Android and PC has to be on the same subnet diff --git a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs index 08000cf33..7086d00e6 100644 --- a/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs +++ b/Editor/Mono/BuildPipeline/DesktopStandaloneBuildWindowExtension.cs @@ -19,15 +19,26 @@ internal abstract class DesktopStandaloneBuildWindowExtension : DefaultBuildWind protected bool m_HasMonoPlayers; protected bool m_HasIl2CppPlayers; protected bool m_HasCoreCLRPlayers; - protected bool m_HasServerPlayers; + protected bool m_HasServerMonoPlayers; + protected bool m_HasServerIl2CppPlayers; protected bool m_IsRunningOnHostPlatform; + public bool MonoPlayersInstalled(NamedBuildTarget namedBuildTarget) + { + return namedBuildTarget == NamedBuildTarget.Server ? m_HasServerMonoPlayers : m_HasMonoPlayers; + } + + public bool Il2CppPlayersInstalled(NamedBuildTarget namedBuildTarget) + { + return namedBuildTarget == NamedBuildTarget.Server ? m_HasServerIl2CppPlayers : m_HasIl2CppPlayers; + } + public static void SetArchitectureForPlatform(BuildTarget buildTarget, OSArchitecture architecture) { EditorUserBuildSettings.SetPlatformSettings(BuildPipeline.GetBuildTargetName(buildTarget), EditorUserBuildSettings.kSettingArchitecture, architecture.ToString().ToLower()); } - public DesktopStandaloneBuildWindowExtension(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasCoreCLRPlayers, bool hasServerPlayers) + public DesktopStandaloneBuildWindowExtension(bool hasMonoPlayers, bool hasIl2CppPlayers, bool hasCoreCLRPlayers, bool hasServerMonoPlayers, bool hasServerIl2CppPlayers) { SetupStandaloneSubtargets(); @@ -35,7 +46,8 @@ public DesktopStandaloneBuildWindowExtension(bool hasMonoPlayers, bool hasIl2Cpp m_HasIl2CppPlayers = hasIl2CppPlayers; m_HasCoreCLRPlayers = hasCoreCLRPlayers; m_HasMonoPlayers = hasMonoPlayers; - m_HasServerPlayers = hasServerPlayers; + m_HasServerMonoPlayers = hasServerMonoPlayers; + m_HasServerIl2CppPlayers = hasServerIl2CppPlayers; } private void SetupStandaloneSubtargets() @@ -84,30 +96,6 @@ struct BuildTargetInfo public OSArchitecture architecture; } - private static Dictionary GetArchitecturesForPlatform(BuildTarget target) - { - switch (target) - { - case BuildTarget.StandaloneWindows: - case BuildTarget.StandaloneWindows64: - return new Dictionary - { - { EditorGUIUtility.TrTextContent("Intel 64-bit"), new BuildTargetInfo - { - buildTarget = BuildTarget.StandaloneWindows64, - architecture = OSArchitecture.x64 - }}, - { EditorGUIUtility.TrTextContent("Intel 32-bit"), new BuildTargetInfo - { - buildTarget = BuildTarget.StandaloneWindows, - architecture = OSArchitecture.x86 - }}, - }; - default: - return null; - } - } - private static BuildTarget DefaultTargetForPlatform(BuildTarget target) { switch (target) @@ -186,30 +174,7 @@ public override void ShowPlatformBuildOptions() int selectedIndex = Math.Max(0, Array.IndexOf(m_StandaloneSubtargets, DefaultTargetForPlatform(selectedTarget))); int newIndex = EditorGUILayout.Popup(m_StandaloneTarget, selectedIndex, m_StandaloneSubtargetStrings); - if (newIndex == selectedIndex) - { - Dictionary architectures = GetArchitecturesForPlatform(selectedTarget); - if (null != architectures) - { - // Display architectures for the current target platform - GUIContent[] architectureNames = new List(architectures.Keys).ToArray(); - int selectedArchitecture = 0; - - // Grab m_Architecture index for currently selected target - foreach (var architecture in architectures) - { - if (architecture.Value.buildTarget == selectedTarget) - { - selectedArchitecture = System.Math.Max(0, System.Array.IndexOf(architectureNames, architecture.Key)); - break; - } - } - - selectedArchitecture = EditorGUILayout.Popup(m_Architecture, selectedArchitecture, architectureNames); - newTarget = architectures[architectureNames[selectedArchitecture]]; - } - } - else + if (newIndex != selectedIndex) { newTarget = DefaultArchitectureForTarget(m_StandaloneSubtargets[newIndex]); } @@ -223,7 +188,6 @@ public override void ShowPlatformBuildOptions() } ShowArchitectureSpecificOptions(); - ShowBackendErrorIfNeeded(); } @@ -246,22 +210,27 @@ protected virtual string GetCannotBuildPlayerInCurrentSetupError() var namedBuildTarget = EditorUserBuildSettingsUtils.CalculateSelectedNamedBuildTarget(); var scriptingBackend = PlayerSettings.GetScriptingBackend(namedBuildTarget); + /* if (namedBuildTarget == NamedBuildTarget.Server) { - if(!m_HasServerPlayers) - return $"Dedicated Server support for {GetHostPlatformName()} is not installed."; + if (scriptingBackend == ScriptingImplementation.Mono2x && !m_HasServerMonoPlayers) + return $"Currently selected scripting backend (Mono) is not installed for {GetHostPlatformName()}."; + + if (scriptingBackend == ScriptingImplementation.IL2CPP && !m_HasServerIl2CppPlayers) + return $"Currently selected scripting backend (IL2CPP) is not installed for {GetHostPlatformName()}."; if (scriptingBackend == ScriptingImplementation.IL2CPP && !m_IsRunningOnHostPlatform) return string.Format("{0} IL2CPP player can only be built on {0}.", GetHostPlatformName()); return null; } + */ switch(scriptingBackend) { case ScriptingImplementation.Mono2x: { - if (!m_HasMonoPlayers) + if (!MonoPlayersInstalled(namedBuildTarget)) return "Currently selected scripting backend (Mono) is not installed."; break; } @@ -276,7 +245,7 @@ protected virtual string GetCannotBuildPlayerInCurrentSetupError() { if (!m_IsRunningOnHostPlatform) return string.Format("{0} IL2CPP player can only be built on {0}.", GetHostPlatformName()); - if (!m_HasIl2CppPlayers) + if (!Il2CppPlayersInstalled(namedBuildTarget)) return "Currently selected scripting backend (IL2CPP) is not installed."; break; } @@ -286,8 +255,6 @@ protected virtual string GetCannotBuildPlayerInCurrentSetupError() } } - - return null; } diff --git a/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs b/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs index 697ad5a5f..ae7bd760b 100644 --- a/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs +++ b/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs @@ -284,7 +284,10 @@ static public void Postprocess(BuildTargetGroup targetGroup, BuildTarget target, // Rethrow exceptions during build postprocessing as BuildFailedException, so we don't pretend the build was fine. throw new BuildFailedException(e); } - report.AddAppendix(props); + if (props != null) + { + report.AddAppendix(props); + } return; } diff --git a/Editor/Mono/Commands/CommandService.cs b/Editor/Mono/Commands/CommandService.cs index a78ca3251..10f56233d 100644 --- a/Editor/Mono/Commands/CommandService.cs +++ b/Editor/Mono/Commands/CommandService.cs @@ -152,8 +152,16 @@ private static IEnumerable ScanAttributes() var commands = new List(); foreach (var mi in TypeCache.GetMethodsWithAttribute()) { - if (!(Delegate.CreateDelegate(typeof(CommandHandler), mi) is CommandHandler callback)) + CommandHandler callback = null; + try + { + callback = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), mi); + } + catch(Exception e) + { + Debug.LogError($"Cannot create CommandHandler from Attribute: {mi.Name} {e.Message}"); continue; + } foreach (var attr in mi.GetCustomAttributes()) { diff --git a/Editor/Mono/ConsoleWindow.cs b/Editor/Mono/ConsoleWindow.cs index 2c3bc2ad0..7bc0387dc 100644 --- a/Editor/Mono/ConsoleWindow.cs +++ b/Editor/Mono/ConsoleWindow.cs @@ -882,10 +882,7 @@ internal static string StacktraceWithHyperlinks(string stacktraceText, int calls filePathPart.Substring(lineIndex + 1, (endLineIndex) - (lineIndex + 1)); string filePath = filePathPart.Substring(0, lineIndex); - textWithHyperlinks.Append(lines[i].Substring(0, filePathIndex)); - textWithHyperlinks.Append(""); - textWithHyperlinks.Append(filePath + ":" + lineString); - textWithHyperlinks.Append(")\n"); + textWithHyperlinks.Append($"{lines[i].Substring(0, filePathIndex)}{filePath}:{lineString})\n"); continue; // continue to evade the default case } diff --git a/Editor/Mono/ContainerWindow.cs b/Editor/Mono/ContainerWindow.cs index f35f18db9..ba4dd6fc6 100644 --- a/Editor/Mono/ContainerWindow.cs +++ b/Editor/Mono/ContainerWindow.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System; using System.Linq; +using UnityEngine.Scripting; namespace UnityEditor { @@ -26,7 +27,7 @@ internal partial class ContainerWindow : ScriptableObject internal int m_DisplayIndex; internal bool m_IsFullscreenContainer; - internal bool m_IsForceTitleBar; + internal bool m_IsMppmCloneWindow; internal bool m_DontSaveToLayout = false; private bool m_HasUnsavedChanges = false; @@ -373,9 +374,10 @@ internal void InternalCloseWindow() DestroyImmediate(this, true); } - internal bool InternalIsForceTitleBar() + [RequiredByNativeCode] + internal bool IsMultiplayerClone() { - return m_IsForceTitleBar; + return m_IsMppmCloneWindow; } private static List FindUnsavedChanges(View view) diff --git a/Editor/Mono/DataMode.cs b/Editor/Mono/DataMode.cs index fb934d692..4ce5f777c 100644 --- a/Editor/Mono/DataMode.cs +++ b/Editor/Mono/DataMode.cs @@ -4,15 +4,15 @@ using System; using System.Collections.Generic; - +using System.Linq; +using UnityEngine; using UnityObject = UnityEngine.Object; using DataModeSupportHandler = UnityEditor.DeclareDataModeSupportAttribute.DataModeSupportHandler; namespace UnityEditor { /// - /// Options for the different modes of an that implements - /// or . + /// Options for the different modes of an . /// // // Dev note: @@ -26,150 +26,260 @@ namespace UnityEditor // Sincerely, // The #dots-editor team // + [Serializable] public enum DataMode // Values must be kept in sync with `DataMode.h` { /// - /// Represents a situation or context in which the usage of data modes is not applicable. + /// Represents a situation or context in which the usage of is not applicable. /// /// - /// This mode informs the docking area that the data modes switch should now be displayed. + /// This mode disables the DataMode switch in the docking area. /// Disabled = 0, /// - /// Uses a mode where only authoring data is available. + /// Uses this mode where only authoring data is available. /// /// /// In this mode, only authoring data is available. When exiting Play mode, Unity retains authoring data. /// Authoring = 1, /// - /// Uses a mode where a mix of authoring and runtime data is available. + /// Uses this mode where a mix of authoring and runtime data is available. /// /// - /// In this mode, a mixture of authoring and runtime data is available. **Important:** When exiting Play mode, - /// Unity loses runtime data. However, it retains any authoring data. + /// In this mode, a mixture of authoring and runtime data is available. + /// When exiting Play mode, Unity loses runtime data. However, it retains any authoring data. /// Mixed = 2, /// - /// Uses a mode where only runtime data is available. + /// Uses this mode where only runtime data is available. /// /// - /// In this mode, only runtime data is available. **Important:** When exiting Play mode, Unity loses runtime - /// data. + /// In this mode, only runtime data is available. When exiting Play mode, Unity loses runtime data. /// Runtime = 3 } /// - /// Implement this interface to allow an to handle changes. + /// Container for the different parameters of the event. + /// + /// DataMode to which the should change. + /// Whether the change was initiated by the DataMode switcher UI + /// at the top-right of the Editor window. + public readonly struct DataModeChangeEventArgs + { + public readonly DataMode nextDataMode; + public readonly bool changedThroughUI; + + public DataModeChangeEventArgs(DataMode nextDataMode, bool changedThroughUI) + { + this.nextDataMode = nextDataMode; + this.changedThroughUI = changedThroughUI; + } + } + + /// + /// Interface with which any can interact with functionalities. + /// To obtain an instance, use > /// /// - /// This interface displays a switch in the docking area when the window is visible and lists the supported modes in - /// the contextual menu for that window. Use this interface if your window only needs to react to direct user - /// interactions with the data mode switch or the contextual menu. If your window needs to change its state based on - /// other factors, like entering or exiting play mode, you should implement - /// instead. + /// This interface displays a switch in the docking area when the window is visible and has + /// more than one supported DataModes. /// - public interface IDataModeHandler + public interface IDataModeController { /// - /// Returns the currently active for the implementor . + /// Returns the currently active for the that + /// owns this instance of IDataModeController. + /// + DataMode dataMode { get; } + + /// + /// Event for subscribing to changes. /// /// - /// Unity does not serialize or store this value. It is the window's responsibility to do so. + /// This method accepts >. + /// For example, you can register to this method to update the contents of the window for the given data mode. /// - DataMode dataMode { get; } + event Action dataModeChanged; /// - /// - /// A list of the s the supports. - /// - /// - /// That list of the s the supports varies based - /// on a number of factors, so it should only contain the modes available to the current context. + /// Updates the list of s that the supports, + /// and sets the preferred DataMode to be used when the DataMode switcher UI is set to Automatic. + /// + /// + /// That list of the DataModes the Editor window supports varies based on a number of factors, + /// so it should only contain the DataModes available to the current context. /// For example, a window might support the and /// modes when in Edit mode, and the and modes when /// in Play mode. A common pattern for that case is to store two lists internally and use /// to select which one to return. - /// - /// - IReadOnlyList supportedDataModes { get; } - - /// - /// Unity calls this method automatically before any call to is made. If the - /// method returns false, is called instead. - /// - /// - /// The for which support is being tested. + /// A list of the supported DataModes. + /// + /// Preferred DataMode to use given the current context when the DataMode switcher UI is set to Automatic. /// - /// - /// Whether the currently supports the specified . - /// - bool IsDataModeSupported(DataMode mode); - - /// - /// Unity calls this method automatically when a user clicks the switch in the docking - /// area tied to the implementing . - /// - /// - /// This method informs the window to change its to whatever mode should come after - /// the current. In most cases, a window only supports two data modes at a time, but it is possible to support - /// all three. Also, a window that supports all three modes might want the switch to only toggle between two - /// specific modes and rely on the contextual menu to change to the third mode. /// - void SwitchToNextDataMode(); + void UpdateSupportedDataModes(IList supportedDataMode, DataMode preferredDataMode); /// - /// Unity calls this method automatically whenever the Editor wants an to be in a - /// specific . + /// Requests a change for the . /// /// - /// By convention, Unity always calls before calling this method. If the - /// data mode is not supported, is called instead. + /// If the DataMode switcher UI is currently set to Automatic, the Editor window also + /// changes to that preferred DataMode. + /// > /// - /// - /// The explicit data mode to which the Editor window should change. + /// + /// The DataMode to which the Editor window should change. /// - void SwitchToDataMode(DataMode mode); + /// + /// Whether the Editor window has accepted the requested DataMode change. + /// > + bool TryChangeDataMode(DataMode newDataMode); + } - /// - /// Unity calls this method automatically whenever going to a requested is impossible - /// because of the result of . - /// - /// - /// This method is a fallback to make sure the is always in a valid state. - /// - /// + // DataModeController handles DataMode related actions internally. + // Each Editor window has a DataModeController instance. + [Serializable] + internal sealed class DataModeController : IDataModeController + { + static readonly DataMode[] k_DefaultModes = Array.Empty(); + + public event Action dataModeChanged; + + [SerializeField] DataMode m_DataMode = DataMode.Disabled; + public DataMode dataMode + { + get => m_DataMode; + private set => m_DataMode = value; + } + + [SerializeField] DataMode m_PreferredDataMode = DataMode.Disabled; + public DataMode preferredDataMode + { + get => m_PreferredDataMode; + private set => m_PreferredDataMode = value; + } + + [SerializeField] DataMode[] m_SupportedDataModes = k_DefaultModes; + public IList supportedDataModes + { + get => m_SupportedDataModes; + private set => m_SupportedDataModes = value.ToArray(); + } + + [SerializeField] internal bool isAutomatic = true; + + readonly List m_DataModeSanitizationCache = new List(3); // Number of modes, minus `Disabled` + + public void UpdateSupportedDataModes(IList supported, DataMode preferred) + { + SanitizeSupportedDataModesList(supported.ToList(), m_DataModeSanitizationCache); + + supportedDataModes = m_DataModeSanitizationCache.Count != 0 ? m_DataModeSanitizationCache : k_DefaultModes; + + preferredDataMode = supportedDataModes.Count switch + { + 0 => DataMode.Disabled, + 1 => supportedDataModes[0], + _ => supportedDataModes.Contains(preferred) ? preferred : supportedDataModes[0] + }; + + if (!isAutomatic || dataMode == preferredDataMode) + return; + + // Recover if automatic + dataMode = preferredDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, false)); + } + + static void SanitizeSupportedDataModesList(IReadOnlyList originalList, List sanitizedList) + { + sanitizedList.Clear(); + + foreach (var mode in originalList) + { + if (mode == DataMode.Disabled) + continue; // Never list `DataMode.Disabled` + + if (sanitizedList.Contains(mode)) + continue; // Prevent duplicate entries + + sanitizedList.Add(mode); + } + + // Ensure we are displaying the data modes in a predefined order, regardless of + // the order in which the user defined their list. + sanitizedList.Sort(); + } + + public bool ShouldDrawDataModesSwitch() + { + return dataMode != DataMode.Disabled + // We don't want to show DataMode switch if there are not + // at least 2 modes supported at the current moment. + && supportedDataModes.Count > 1; + } + + public bool TryChangeDataMode(DataMode newDataMode) + { + // Only change if currently in automatic mode + if (!isAutomatic || dataMode == newDataMode || !supportedDataModes.Contains(newDataMode)) + return false; + + dataMode = newDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(newDataMode, false)); + return true; + } + + // Invoked when user interacts with the DataMode dropdown menu, for internal use only. + internal void SwitchToAutomatic() + { + if (isAutomatic) + return; + + isAutomatic = true; + + if (dataMode == preferredDataMode) + return; + + // If the DataMode is not supported in current context, we fall back to default one. + dataMode = preferredDataMode; + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, true)); + } + + // Invoked when user interacts with the DataMode dropdown men, for internal use only. + internal void SwitchToStickyDataMode(DataMode stickyDataMode) + { + isAutomatic = false; + + if (dataMode == stickyDataMode) + return; + + dataMode = supportedDataModes.Contains(stickyDataMode) + ? stickyDataMode + : preferredDataMode; + + dataModeChanged?.Invoke(new DataModeChangeEventArgs(dataMode, true)); + } + } + + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("IDataModeHandler has been deprecated, please use EditorWindow.dataModeController instead.", false)] + public interface IDataModeHandler + { + DataMode dataMode { get; } + IReadOnlyList supportedDataModes { get; } + bool IsDataModeSupported(DataMode mode); + void SwitchToNextDataMode(); + void SwitchToDataMode(DataMode mode); void SwitchToDefaultDataMode(); } - /// - /// Implement this interface to allow an to handle changes and - /// alter its internally. - /// - /// - /// This interface displays a switch in the docking area when the window is visible and lists the supported modes in - /// the contextual menu for that window. Use this interface if your window needs to control its mode internally - /// based on factors other than the user directly interacting with the data mode switch or the contextual menu, for - /// example, entering or exiting Play mode. If your window does not need to control its own mode, use - /// instead. - /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("IDataModeHandlerAndDispatcher has been deprecated, please use EditorWindow.dataModeController instead.", false)] public interface IDataModeHandlerAndDispatcher : IDataModeHandler { - /// - /// - /// Calls the methods in its invocation list when the changes due to an external factor - /// and passes the new data mode as an argument. - /// - /// - /// An external factor refers to any action which results in a data mode change that Unity did not initiate - /// directly through calling either , - /// , or . - /// - /// - /// For example, when entering or exiting Play mode, some windows might want to force a data mode switch. - /// - /// event Action dataModeChanged; } diff --git a/Editor/Mono/Delayer.cs b/Editor/Mono/Delayer.cs index df2fd4910..342d242cd 100644 --- a/Editor/Mono/Delayer.cs +++ b/Editor/Mono/Delayer.cs @@ -10,7 +10,7 @@ class Delayer { private long m_LastExecutionTime; private Action m_Action; - private readonly double m_DebounceDelay; + private readonly long m_DebounceDelay; private object m_Context; private readonly bool m_IsThrottle; private readonly bool m_FirstExecuteImmediate; @@ -63,7 +63,7 @@ public void Execute(object context = null) private Delayer(Action action, double delay, bool isThrottle, bool firstExecuteImmediate) { m_Action = action; - m_DebounceDelay = delay; + m_DebounceDelay = TimeSpan.FromSeconds(delay).Ticks; m_IsThrottle = isThrottle; m_FirstExecuteImmediate = firstExecuteImmediate; } @@ -74,41 +74,44 @@ public void Dispose() EditorApplication.tick -= Throttle; m_Context = null; m_Action = null; + m_DelayInProgress = false; } private void Debounce() { - m_DelayInProgress = false; - EditorApplication.tick -= Debounce; var currentTime = DateTime.UtcNow.Ticks; if (m_LastExecutionTime != 0 && DelayHasPassed(currentTime)) { + m_DelayInProgress = false; + EditorApplication.tick -= Debounce; m_Action?.Invoke(m_Context); m_LastExecutionTime = 0; } else { - EditorApplication.tick += Debounce; + if (!m_DelayInProgress) + EditorApplication.tick += Debounce; m_DelayInProgress = true; } } private void Throttle() { - m_DelayInProgress = false; - EditorApplication.tick -= Throttle; var currentTime = DateTime.UtcNow.Ticks; if (m_FirstExecuteImmediate) { if (m_LastExecutionTime == 0 || DelayHasPassed(currentTime)) { + m_DelayInProgress = false; + EditorApplication.tick -= Throttle; m_Action?.Invoke(m_Context); m_LastExecutionTime = currentTime; } else { - EditorApplication.tick += Throttle; + if (!m_DelayInProgress) + EditorApplication.tick += Throttle; m_DelayInProgress = true; } } @@ -116,6 +119,8 @@ private void Throttle() { if (m_LastExecutionTime != 0 && DelayHasPassed(currentTime)) { + m_DelayInProgress = false; + EditorApplication.tick -= Throttle; m_Action?.Invoke(m_Context); m_LastExecutionTime = 0; } @@ -123,7 +128,8 @@ private void Throttle() { if (m_LastExecutionTime == 0) m_LastExecutionTime = currentTime; - EditorApplication.tick += Throttle; + if (!m_DelayInProgress) + EditorApplication.tick += Throttle; m_DelayInProgress = true; } } @@ -132,7 +138,7 @@ private void Throttle() private bool DelayHasPassed(long currentTime) { var timeSpan = new TimeSpan(currentTime - m_LastExecutionTime); - return timeSpan.TotalSeconds >= m_DebounceDelay; + return timeSpan.Ticks >= m_DebounceDelay; } } } diff --git a/Editor/Mono/EditorApplication.cs b/Editor/Mono/EditorApplication.cs index a7592073b..9a5e801c6 100644 --- a/Editor/Mono/EditorApplication.cs +++ b/Editor/Mono/EditorApplication.cs @@ -244,6 +244,8 @@ public static event Action projectChanged internal static CallbackFunction assetBundleNameChanged; + internal static CallbackFunction fileMenuSaved; + // Delegate for changed keyboard modifier keys. public static CallbackFunction modifierKeysChanged; @@ -389,6 +391,15 @@ internal static void RequestRepaintAllViews() view.Repaint(); } + internal static void RequestRepaintAllTexts() + { + foreach (GUIView view in Resources.FindObjectsOfTypeAll(typeof(GUIView))) + { + view.Repaint(); + view.RepaintUITKText(); + } + } + static void Internal_CallHierarchyHasChanged() { #pragma warning disable 618 @@ -445,6 +456,12 @@ static void Internal_PlayModeStateChanged(PlayModeStateChange state) evt(state); } + [RequiredByNativeCode] + internal static void Internal_FileMenuSaved() + { + fileMenuSaved?.Invoke(); + } + static void Internal_CallKeyboardModifiersChanged() { modifierKeysChanged?.Invoke(); diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 9aa30d07a..ef1284972 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -208,6 +208,8 @@ private enum DragCandidateState internal static SavedBool s_ShowRepaintDots = new SavedBool("ShowRepaintDots", true); + internal static readonly Regex s_HyperlinkRegex = new Regex(@"(?<=\b=')[^']*"); + static class Styles { public static Texture2D prefabOverlayAddedIcon = EditorGUIUtility.LoadIcon("PrefabOverlayAdded Icon"); @@ -520,7 +522,7 @@ internal bool IsEditingControl(int id) return GUIUtility.keyboardControl == id && controlID == id && s_ActuallyEditing && GUIView.current.hasFocus; } - public virtual void BeginEditing(int id, string newText, Rect _position, GUIStyle _style, bool _multiline, bool passwordField) + public virtual void BeginEditing(int id, string newText, Rect position, GUIStyle style, bool multiline, bool passwordField) { if (IsEditingControl(id)) { @@ -532,9 +534,9 @@ public virtual void BeginEditing(int id, string newText, Rect _position, GUIStyl activeEditor = this; controlID = id; text = s_OriginalText = newText; - multiline = _multiline; - style = _style; - position = _position; + isMultiline = multiline; + this.position = position; + this.style = style; isPasswordField = passwordField; s_ActuallyEditing = true; scrollOffset = Vector2.zero; @@ -982,12 +984,18 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit // If the editor is already set up, we just need to sync position, etc... if (editor.IsEditingControl(id)) { + // Fast path flag to ensure that we only update the scroll offset if the text area grew (dynamic height) + bool requireUpdateScrollOffset = editor.position.height != position.height; editor.position = position; editor.style = style; editor.controlID = id; - editor.multiline = multiline; + editor.isMultiline = multiline; editor.isPasswordField = passwordField; editor.DetectFocusChange(); + editor.UpdateTextHandle(); + + if (requireUpdateScrollOffset) + editor.UpdateScrollOffset(); } else if (EditorGUIUtility.editingTextField || (evt.GetTypeForControl(id) == EventType.ExecuteCommand && evt.commandName == EventCommandNames.NewKeyboardFocus)) { @@ -1119,7 +1127,12 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit { // Extract hyperlink info Dictionary hyperLinkData; - if (HasClickedOnHyperlink(text, editor.cursorIndex, editor, out hyperLinkData)) // Check if the cursor is between hyperlink tags and store the hyperlink info (tag arguments in a dictionary) + + if (editor.HasClickedOnHREF(Event.current.mousePosition, out string href)) + { + Application.OpenURL(href); + } + else if (HasClickedOnHyperlink(editor, out hyperLinkData)) // Check if the cursor is between hyperlink tags and store the hyperlink info (tag arguments in a dictionary) { // Raise event with the info var window = GUIView.current is HostView hostView ? hostView.actualView : null; @@ -1358,7 +1371,6 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit drawText = passwordField ? "".PadRight(text.Length, '*') : text; } - if (!string.IsNullOrEmpty(s_UnitString) && !passwordField) drawText += " " + s_UnitString; @@ -1392,6 +1404,10 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.Text); } + break; + case EventType.ScrollWheel: + // Scroll offset might need to be updated + editor.UpdateScrollOffset(); break; } @@ -1401,9 +1417,6 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit GUIUtility.textFieldInput = EditorGUIUtility.editingTextField; } - // Scroll offset might need to be updated - editor.UpdateScrollOffsetIfNeeded(evt); - changed = false; if (mayHaveChanged) { @@ -1441,58 +1454,31 @@ internal static string DoTextField(RecycledTextEditor editor, int id, Rect posit return origText; } - private static bool HasClickedOnHyperlink(string text, int cursorIndex, RecycledTextEditor editor, out Dictionary hyperLinkData) + private static bool HasClickedOnHyperlink(RecycledTextEditor editor, out Dictionary hyperLinkData) { - Vector2 mousePosition = Event.current.mousePosition; hyperLinkData = new Dictionary(); - if (cursorIndex > 0) - { - bool hitHyperlink = false; - foreach (var rect in editor.GetHyperlinksRect()) - { - if (rect.Contains(mousePosition)) - { - hitHyperlink = true; - break; - } - } - - if (hitHyperlink) - { - int indexBeginTag = text.Substring(0, editor.cursorIndex) - .LastIndexOf("= 0) - { - int indexBeginTagClose = - text.Substring(indexBeginTag, cursorIndex - indexBeginTag).IndexOf('>'); + Vector2 mousePosition = Event.current.mousePosition; + if (!editor.HasClickedOnLink(mousePosition, out string link)) + return false; - string beginTag = text.Substring(indexBeginTag, indexBeginTagClose); - // Regex to find the attribute value - Regex regex = new Regex(@"(?<=\b="")[^""]*"); - MatchCollection matches = regex.Matches(beginTag); + MatchCollection matches = s_HyperlinkRegex.Matches(link); - int endPreviousAttributeIndex = 0; - // for each attribute we need to find the attribute name - foreach (Match match in matches) - { - // We are only working on the text between the previous attribute and the current - string namePart = beginTag.Substring(endPreviousAttributeIndex, - (match.Index - 2) - endPreviousAttributeIndex); // -2 is the character before =" - int indexName = namePart.LastIndexOf(' ') + 1; - string name = namePart.Substring(indexName); - // Add the name of the attribute and its value in the dictionary - hyperLinkData.Add(name, match.Value); - - endPreviousAttributeIndex = match.Index + match.Value.Length + 1; - } + int endPreviousAttributeIndex = 0; + // for each attribute we need to find the attribute name + foreach (Match match in matches) + { + // We are only working on the text between the previous attribute and the current + string namePart = link.Substring(endPreviousAttributeIndex, + (match.Index - 2) - endPreviousAttributeIndex); // -2 is the character before =" + int indexName = namePart.LastIndexOf(' ') + 1; + string name = namePart.Substring(indexName); + // Add the name of the attribute and its value in the dictionary + hyperLinkData.Add(name, match.Value); - return true; - } - } + endPreviousAttributeIndex = match.Index + match.Value.Length + 1; } - return false; + + return true; } public static event Action hyperLinkClicked; @@ -2054,7 +2040,7 @@ internal static string ScrollableTextAreaInternal(Rect position, string text, re else { //Move the Editor offset to match our scrollbar - s_RecycledEditor.scrollOffset.y = scrollPosition.y; + s_RecycledEditor.scrollOffset = scrollPosition; } } bool dummy; @@ -4631,7 +4617,8 @@ static Vector3 LinkedVector3Field(Rect position, Vector3 value, bool proportiona s_Vector3Floats[2] = value.z; position.height = kSingleLineHeight; BeginChangeCheck(); - LockingMultiFloatFieldInternal(position, proportionalScale, mixedValues, s_XYZLabels, s_Vector3Floats, new float[] {initialScale.x, initialScale.y, initialScale.z}, property, EditorGUI.CalcPrefixLabelWidth(s_XYZLabels[0]) + 3); + const float kPrefixWidthOffset = 3.65f; + LockingMultiFloatFieldInternal(position, proportionalScale, mixedValues, s_XYZLabels, s_Vector3Floats, new float[] {initialScale.x, initialScale.y, initialScale.z}, property, EditorGUI.CalcPrefixLabelWidth(s_XYZLabels[0]) + kPrefixWidthOffset); if (EndChangeCheck()) { valueAfterChangeCheck.x = s_Vector3Floats[0]; diff --git a/Editor/Mono/EditorGUIUtility.cs b/Editor/Mono/EditorGUIUtility.cs index 20f324158..5e40cc253 100644 --- a/Editor/Mono/EditorGUIUtility.cs +++ b/Editor/Mono/EditorGUIUtility.cs @@ -1162,7 +1162,7 @@ internal static void NotifyLanguageChanged(SystemLanguage newLanguage) L10n.ClearCache(); EditorUtility.Internal_UpdateMenuTitleForLanguage(newLanguage); LocalizationDatabase.currentEditorLanguage = newLanguage; - PanelTextSettings.UpdateLocalizationFontAsset(); + EditorTextSettings.UpdateLocalizationFontAsset(); EditorApplication.RequestRepaintAllViews(); } diff --git a/Editor/Mono/EditorTextSettings.cs b/Editor/Mono/EditorTextSettings.cs new file mode 100644 index 000000000..315f562cd --- /dev/null +++ b/Editor/Mono/EditorTextSettings.cs @@ -0,0 +1,75 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.TextCore.Text; +using UnityEditor.Experimental; + +namespace UnityEditor +{ + + /// + /// Represents text rendering settings for the editor + /// + [InitializeOnLoad] + internal class EditorTextSettings : TextSettings + { + private static EditorTextSettings s_DefaultTextSettings; + + const string k_Platform = + " - Linux"; + + static EditorTextSettings() + { + IMGUITextHandle.GetEditorTextSettings = () => defaultTextSettings; + IMGUITextHandle.GetEditorTextSharpness = (string fontAssetName) => EditorPrefs.GetFloat($"EditorTextSharpness_{fontAssetName}", 0.0f); + IMGUITextHandle.GetEditorFont = () => EditorResources.GetFont(FontDef.Style.Normal); + } + + internal static EditorTextSettings defaultTextSettings + { + get + { + if (s_DefaultTextSettings == null) + { + s_DefaultTextSettings = EditorGUIUtility.Load(s_DefaultEditorTextSettingPath) as EditorTextSettings; + if (s_DefaultTextSettings) + UpdateLocalizationFontAsset(); + } + + return s_DefaultTextSettings; + } + } + + internal static void UpdateLocalizationFontAsset() + { + var localizationAssetPathPerSystemLanguage = new Dictionary() + { + { SystemLanguage.English, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/English{k_Platform}.asset" }, + { SystemLanguage.Japanese, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/Japanese{k_Platform}.asset" }, + { SystemLanguage.ChineseSimplified, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/ChineseSimplified{k_Platform}.asset" }, + { SystemLanguage.ChineseTraditional, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/ChineseTraditional{k_Platform}.asset" }, + { SystemLanguage.Korean, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/Korean{k_Platform}.asset" } + }; + + var globalFallbackAssetPath = $"UIPackageResources/FontAssets/DynamicOSFontAssets/GlobalFallback/GlobalFallback{k_Platform}.asset"; + + FontAsset localizationAsset = null; + + if (localizationAssetPathPerSystemLanguage.ContainsKey(LocalizationDatabase.currentEditorLanguage)) + { + localizationAsset = EditorGUIUtility.Load(localizationAssetPathPerSystemLanguage[LocalizationDatabase.currentEditorLanguage]) as FontAsset; + } + + var globalFallbackAsset = EditorGUIUtility.Load(globalFallbackAssetPath) as FontAsset; + + defaultTextSettings.fallbackFontAssets[0] = localizationAsset; + defaultTextSettings.fallbackFontAssets[defaultTextSettings.fallbackFontAssets.Count - 1] = globalFallbackAsset; + } + + internal static readonly string s_DefaultEditorTextSettingPath = "UIPackageResources/Editor Text Settings.asset"; + } +} diff --git a/Editor/Mono/EditorUserBuildSettings.bindings.cs b/Editor/Mono/EditorUserBuildSettings.bindings.cs index b7188b4de..6a9c75826 100644 --- a/Editor/Mono/EditorUserBuildSettings.bindings.cs +++ b/Editor/Mono/EditorUserBuildSettings.bindings.cs @@ -386,9 +386,6 @@ private EditorUserBuildSettings() {} [NativeMethod("GetActiveSubTargetFor")] internal static extern int GetActiveSubtargetFor(BuildTarget target); - [NativeMethod("SetActiveSubTargetFor")] - internal static extern void SetActiveSubtargetFor(BuildTarget target, int subtarget); - // QNX OS Version public static extern QNXOsVersion selectedQnxOsVersion { @@ -423,8 +420,30 @@ public static extern EmbeddedLinuxArchitecture selectedEmbeddedLinuxArchitecture public static extern string remoteDeviceExports { get; set; } public static extern string pathOnRemoteDevice { get; set; } - // The currently selected target for a standalone build. - public static extern BuildTarget selectedStandaloneTarget + public static BuildTarget selectedStandaloneTarget + { + get { return internal_SelectedStandaloneTarget; } + set + { + string platformName = BuildPipeline.GetBuildTargetName(value); + var architecture = GetPlatformSettings(platformName, kSettingArchitecture).ToLower(); + switch (value) + { + case BuildTarget.StandaloneWindows: + if (architecture != "x86") + SetPlatformSettings(platformName, kSettingArchitecture, OSArchitecture.x86.ToString()); + break; + case BuildTarget.StandaloneWindows64: + if (architecture != "x64" && architecture != "arm64") + SetPlatformSettings(platformName, kSettingArchitecture, OSArchitecture.x64.ToString()); + break; + } + + internal_SelectedStandaloneTarget = value; + } + } + + private static extern BuildTarget internal_SelectedStandaloneTarget { [NativeMethod("GetSelectedStandaloneTarget")] get; @@ -720,9 +739,14 @@ public static extern WSABuildAndRunDeployTarget wsaBuildAndRunDeployTarget internal static extern BuildTargetGroup activeBuildTargetGroup { get; } [NativeMethod("SwitchActiveBuildTargetSync")] - public static extern bool SwitchActiveBuildTarget(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtarget(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + public static bool SwitchActiveBuildTarget(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtarget(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); + [NativeMethod("SwitchActiveBuildTargetAsync")] - public static extern bool SwitchActiveBuildTargetAsync(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtargetAsync(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + public static bool SwitchActiveBuildTargetAsync(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtargetAsync(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); public static bool SwitchActiveBuildTarget(NamedBuildTarget namedBuildTarget, BuildTarget target) => BuildPlatforms.instance.BuildPlatformFromNamedBuildTarget(namedBuildTarget).SetActive(target); @@ -731,7 +755,9 @@ public static bool SwitchActiveBuildTarget(NamedBuildTarget namedBuildTarget, Bu // validating if support for it is installed. However it does not do things like script recompile // or domain reload -- generally only useful for asset import testing. [NativeMethod("SwitchActiveBuildTargetSyncNoCheck")] - internal static extern bool SwitchActiveBuildTargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target); + internal static extern bool SwitchActiveBuildTargetAndSubtargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target, int subtarget); + internal static bool SwitchActiveBuildTargetNoCheck(BuildTargetGroup targetGroup, BuildTarget target) + => SwitchActiveBuildTargetAndSubtargetNoCheck(targetGroup, target, EditorUserBuildSettings.GetActiveSubtargetFor(target)); // DEFINE directives for the compiler. public static extern string[] activeScriptCompilationDefines diff --git a/Editor/Mono/EditorUserBuildSettingsUtils.cs b/Editor/Mono/EditorUserBuildSettingsUtils.cs index 16f66b64e..3c493bc56 100644 --- a/Editor/Mono/EditorUserBuildSettingsUtils.cs +++ b/Editor/Mono/EditorUserBuildSettingsUtils.cs @@ -108,7 +108,7 @@ public static bool SetActive(this BuildPlatform buildPlatform, BuildTarget targe } if (buildPlatform is BuildPlatformWithSubtarget) - EditorUserBuildSettings.SetActiveSubtargetFor(target, ((BuildPlatformWithSubtarget)buildPlatform).subtarget); + return EditorUserBuildSettings.SwitchActiveBuildTargetAndSubtarget(buildPlatform.namedBuildTarget.ToBuildTargetGroup(), target, ((BuildPlatformWithSubtarget)buildPlatform).subtarget); return EditorUserBuildSettings.SwitchActiveBuildTarget(buildPlatform.namedBuildTarget.ToBuildTargetGroup(), target); } diff --git a/Editor/Mono/EditorWindow.cs b/Editor/Mono/EditorWindow.cs index 4abbd6502..c4e178d74 100644 --- a/Editor/Mono/EditorWindow.cs +++ b/Editor/Mono/EditorWindow.cs @@ -44,6 +44,12 @@ public partial class EditorWindow : ScriptableObject [HideInInspector] internal Rect m_Pos = new Rect(0, 0, 320, 550); + [SerializeField] + internal DataModeController m_SerializedDataModeController; + public IDataModeController dataModeController => GetDataModeController_Internal(); // For each editor window. + internal DataModeController GetDataModeController_Internal() // For HostView to use internally. + => m_SerializedDataModeController ??= new DataModeController(); + private VisualElement m_UIRootElement; internal VisualElement baseRootVisualElement => m_UIRootElement == null diff --git a/Editor/Mono/GI/InputExtraction.bindings.cs b/Editor/Mono/GI/InputExtraction.bindings.cs new file mode 100644 index 000000000..d35374791 --- /dev/null +++ b/Editor/Mono/GI/InputExtraction.bindings.cs @@ -0,0 +1,254 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Runtime.InteropServices; +using UnityEngine.Bindings; + +namespace UnityEditor.LightBaking +{ + [NativeHeader("Editor/Src/GI/InputExtraction/InputExtraction.Bindings.h")] + [StaticAccessor("InputExtractionBindings", StaticAccessorType.DoubleColon)] + internal static class InputExtraction + { + [StructLayout(LayoutKind.Sequential)] + public class SourceMap : IDisposable + { + internal IntPtr m_Ptr; + + public SourceMap() + { + m_Ptr = Internal_Create(); + } + + ~SourceMap() + { + Destroy(); + } + + public void Dispose() + { + Destroy(); + GC.SuppressFinalize(this); + } + + void Destroy() + { + if (m_Ptr != IntPtr.Zero) + { + Internal_Destroy(m_Ptr); + m_Ptr = IntPtr.Zero; + } + } + + public extern int GetInstanceIndex(int instanceID); + public extern int GetInstanceInstanceID(int instanceIndex); + + public extern int GetTreeCount(); + public extern int GetTreeInstanceIndex(int treeIndex); + + public extern int GetLightIndex(int instanceID); + public extern int GetLightInstanceID(int lightIndex); + + static extern IntPtr Internal_Create(); + static extern void Internal_Destroy(IntPtr ptr); + } + + public static extern bool ExtractFromScene(string outputFolderPath, LightBaker.BakeInput input, SourceMap map); + + private static string LookupGameObjectName(SourceMap map, int instanceIndex) + { + if (map == null) + return ""; + int instanceID = map.GetInstanceInstanceID(instanceIndex); + if (instanceID == 0) + return ""; + Object obj = EditorUtility.InstanceIDToObject(instanceID); + if (obj == null) + return ""; + else if (obj is UnityEngine.GameObject go) + { + return go.name; + } + else if (obj is UnityEngine.Component component) + { + return component.gameObject.name; + } + return ""; + } + + public static string LogInstances(LightBaker.BakeInput bakeInput, SourceMap map) + { + if (bakeInput is null) + return string.Empty; + string message = string.Empty; + message += $" instance count\t: {bakeInput.instanceCount}\n"; + for (int i = 0; i < bakeInput.instanceCount; ++i) + { + message += $" Instance [{i}] [{LookupGameObjectName(map, i)}]:\n"; + LightBaker.Instance instance = bakeInput.instance((uint)i); + message += $" mesh type\t\t: {instance.meshType}\n"; + if (instance.meshType == LightBaker.MeshType.MeshRenderer) + message += $" mesh index\t: {instance.meshIndex}\n"; + else if (instance.meshType == LightBaker.MeshType.Terrain) + message += $" terrain index\t: {instance.terrainIndex}\n"; + message += $" transform\t\t: {instance.transform.GetRow(0)}\n"; + message += $" \t\t: {instance.transform.GetRow(1)}\n"; + message += $" \t\t: {instance.transform.GetRow(2)}\n"; + message += $" \t\t: {instance.transform.GetRow(3)}\n"; + message += $" cast shadows\t: {instance.castShadows}\n"; + message += $" receive shadows\t: {instance.receiveShadows}\n"; + if (instance.meshType == LightBaker.MeshType.MeshRenderer) + { + message += $" odd negative scale\t: {instance.oddNegativeScale}\n"; + message += $" lod group\t\t: {instance.lodGroup}\n"; + message += $" lod mask\t\t: {instance.lodMask}\n"; + } + message += $" submesh count\t: {instance.submeshMaterialIndices.Length}\n"; + string indices = string.Empty; + for (int j = 0; j < instance.submeshMaterialIndices.Length; ++j) + indices += instance.submeshMaterialIndices[j] + (j < (instance.submeshMaterialIndices.Length - 1) ? ", " : ""); + message += $" submesh mat idxs\t: {{{indices}}}\n"; + } + return message; + } + + public static string LogSceneMaterials(LightBaker.BakeInput bakeInput) + { + if (bakeInput is null) + return string.Empty; + string message = string.Empty; + message += $" material count\t: {bakeInput.materialCount}\n"; + for (int i = 0; i < bakeInput.materialCount; ++i) + { + message += $" Material [{i}]:\n"; + message += $" doubleSidedGI\t: {bakeInput.GetMaterial((uint)i).doubleSidedGI}\n"; + message += $" transmissionChannels\t: {bakeInput.GetMaterial((uint)i).transmissionChannels}\n"; + message += $" transmissionType\t: {bakeInput.GetMaterial((uint)i).transmissionType}\n"; + } + return message; + } + + public static string LogSceneCookies(LightBaker.BakeInput bakeInput) + { + if (bakeInput is null) + return string.Empty; + string message = string.Empty; + + message += $" cookie tex count\t: {bakeInput.GetCookieCount()}\n"; + for (int i = 0; i < bakeInput.GetCookieCount(); ++i) + { + LightBaker.CookieData cookie = bakeInput.GetCookieData((uint)i); + message += $" CookieTexture [{i}]:\n"; + message += $" resolution\t\t: {cookie.resolution.width} x {cookie.resolution.height}\n"; + message += $" pixelStride\t\t: {cookie.pixelStride}\n"; + message += $" slices\t\t: {cookie.slices}\n"; + message += $" repeat\t\t: {cookie.repeat}\n"; + } + return message; + } + + public static string LogSceneLights(LightBaker.BakeInput bakeInput) + { + if (bakeInput is null) + return string.Empty; + string message = string.Empty; + message += $" light count\t\t: {bakeInput.GetLightCount()}\n"; + for (int i = 0; i < bakeInput.GetLightCount(); ++i) + { + LightBaker.Light light = bakeInput.GetLight((uint)i); + message += $" Light [{i}]:\n"; + message += $" color\t\t: {light.color}\n"; + message += $" indirect color\t: {light.indirectColor}\n"; + message += $" orientation\t: {light.orientation}\n"; + message += $" position\t\t: {light.position}\n"; + message += $" range\t\t: {light.range}\n"; + message += $" cookie index\t: {light.cookieTextureIndex}\n"; + message += $" cookie scale\t: {light.cookieScale}\n"; + message += $" cone angle\t: {light.coneAngle}\n"; + message += $" inner cone angle\t: {light.innerConeAngle}\n"; + message += $" shape0\t\t: {light.shape0}\n"; + message += $" shape1\t\t: {light.shape1}\n"; + message += $" type\t\t: {light.type}\n"; + message += $" mode\t\t: {light.mode}\n"; + message += $" falloff\t\t: {light.falloff}\n"; + message += $" angular falloff\t: {light.angularFalloff}\n"; + message += $" casts shadows\t: {light.castsShadows}\n"; + message += $" shadowmask chnl\t: {light.shadowMaskChannel}\n"; + } + return message; + } + + public static string LogSampleCounts(LightBaker.SampleCount sampleCount) + { + string message = string.Empty; + message += $" direct\t\t: {sampleCount.directSampleCount}\n"; + message += $" indirect\t\t: {sampleCount.indirectSampleCount}\n"; + message += $" environment\t: {sampleCount.environmentSampleCount}\n"; + return message; + } + + public static string LogSceneSettings(LightBaker.BakeInput bakeInput) + { + if (bakeInput is null) + return string.Empty; + var lightingSettings = bakeInput.GetLightingSettings(); + string message = string.Empty; + message += $" lighting settings\t:\n"; + message += $" lightmap sample counts:\n"; + message += LogSampleCounts(lightingSettings.lightmapSampleCounts); + message += $" lightprobe sample counts:\n"; + message += LogSampleCounts(lightingSettings.probeSampleCounts); + message += $" min bounces\t: {lightingSettings.minBounces}\n"; + message += $" max bounces\t: {lightingSettings.maxBounces}\n"; + message += $" lightmap bake mode\t: {lightingSettings.lightmapBakeMode}\n"; + message += $" mixed lighting mode\t: {lightingSettings.mixedLightingMode}\n"; + message += $" ao enabled\t\t: {lightingSettings.aoEnabled}\n"; + message += $" ao distance\t\t: {lightingSettings.aoDistance}\n"; + return message; + } + + public static string LogScene(LightBaker.BakeInput bakeInput, SourceMap map) + { + if (bakeInput is null) + return string.Empty; + string message = string.Empty; + message += LogSceneSettings(bakeInput); + message += LogInstances(bakeInput, map); + message += $" mesh count\t\t: {bakeInput.meshCount}\n"; + message += $" terrain count\t\t: {bakeInput.terrainCount}\n"; + message += $" heightmap count\t: {bakeInput.heightmapCount}\n"; + message += $" holemap count\t: {bakeInput.holemapCount}\n"; + message += LogSceneMaterials(bakeInput); + message += $" albedo tex count\t: {bakeInput.albedoTextureCount}\n"; + for (int i = 0; i < bakeInput.albedoTextureCount; ++i) + { + message += $" AlbedoTexture [{i}]:\n"; + message += $" resolution\t\t: {bakeInput.GetAlbedoTextureData((uint)i).resolution.width} x {bakeInput.GetAlbedoTextureData((uint)i).resolution.height}\n"; + } + message += $" emissive tex count\t: {bakeInput.emissiveTextureCount}\n"; + for (int i = 0; i < bakeInput.emissiveTextureCount; ++i) + { + message += $" EmissiveTexture [{i}]:\n"; + message += $" resolution\t\t: {bakeInput.GetEmissiveTextureData((uint)i).resolution.width} x {bakeInput.GetEmissiveTextureData((uint)i).resolution.height}\n"; + } + message += $" transmissive tex count\t: {bakeInput.transmissiveTextureCount}\n"; + for (int i = 0; i < bakeInput.transmissiveTextureCount; ++i) + { + message += $" TransmissiveTexture [{i}]:\n"; + message += $" resolution\t\t: {bakeInput.GetTransmissiveTextureData((uint)i).resolution.width} x {bakeInput.GetTransmissiveTextureData((uint)i).resolution.height}\n"; + } + message += LogSceneCookies(bakeInput); + message += LogSceneLights(bakeInput); + message += $" lightmap count\t: {bakeInput.lightmapCount}\n"; + for (int i = 0; i < bakeInput.lightmapCount; ++i) + { + message += $" Lightmap [{i}]:\n"; + message += $" resolution\t\t: {bakeInput.lightmapResolution((uint)i).width} x {bakeInput.lightmapResolution((uint)i).height}\n"; + message += $" instance count\t: {bakeInput.lightmapInstanceCount((uint)i)}\n"; + } + return message; + } + } +} diff --git a/Editor/Mono/GI/LightBaker.bindings.cs b/Editor/Mono/GI/LightBaker.bindings.cs new file mode 100644 index 000000000..a96050315 --- /dev/null +++ b/Editor/Mono/GI/LightBaker.bindings.cs @@ -0,0 +1,564 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Runtime.InteropServices; +using UnityEngine; +using UnityEngine.Bindings; + +namespace UnityEditor.LightBaking +{ + [NativeHeader("Editor/Src/GI/LightBaker/LightBaker.Bindings.h")] + [StaticAccessor("LightBakerBindings", StaticAccessorType.DoubleColon)] + internal static class LightBaker + { + public enum ResultType : UInt32 + { + Success = 0, + Cancelled, + JobFailed, + OutOfMemory, + InvalidInput, + LowLevelAPIFailure, + IOFailed, + Undefined + } + + public struct Result + { + public ResultType type; + public String message; + public override string ToString() + { + if (message.Length == 0) + return $"Result type: '{type}'"; + else + return $"Result type: '{type}', message: '{message}'"; + + } + } + + + public enum Backend + { + CPU = 0, + GPU = 1 + } + public enum TransmissionChannels + { + Red = 0, + Alpha = 1, + AlphaCutout = 2, + RGB = 3, + None = 4 + } + public enum TransmissionType + { + Opacity = 0, + Transparency = 1, + None = 2 + } + public enum MeshType + { + Terrain = 0, + MeshRenderer = 1 + } + public enum MixedLightingMode + { + IndirectOnly = 0, + Subtractive = 1, + Shadowmask = 2, + }; + public enum LightmapBakeMode + { + NonDirectional = 0, + CombinedDirectional = 1 + }; + [Flags] + public enum ProbeRequestOutputType : UInt32 + { + RadianceDirect = 1 << 0, + RadianceIndirect = 1 << 1, + Validity = 1 << 2, + MixedLightOcclusion = 1 << 3, + LightProbeOcclusion = 1 << 4, + EnvironmentOcclusion = 1 << 5, + Depth = 1 << 6, + All = 0xFFFFFFFF + }; + public struct ProbeRequest + { + public ProbeRequestOutputType outputTypeMask; + public UInt64 positionOffset; + public UInt64 positionLength; + public float pushoff; + public string outputFolderPath; + }; + [Flags] + public enum LightmapRequestOutputType : UInt32 + { + IrradianceIndirect = 1 << 0, + IrradianceDirect = 1 << 1, + IrradianceEnvironment = 1 << 2, + Occupancy = 1 << 3, + Validity = 1 << 4, + DirectionalityIndirect = 1 << 5, + DirectionalityDirect = 1 << 6, + AmbientOcclusion = 1 << 7, + Shadowmask = 1 << 8, + Normal = 1 << 9, + ChartIndex = 1 << 10, + OverlapPixelIndex = 1 << 11, + All = 0xFFFFFFFF + }; + public enum TilingMode + { // Assuming a 4k lightmap (16M texels), the tiling will yield the following chunk sizes: + None = 0, // 4k * 4k = 16M texels + Quarter = 1, // 2k * 2k = 4M texels + Sixteenth = 2, // 1k * 1k = 1M texels + Sixtyfourth = 3, // 512 * 512 = 262k texels + TwoHundredFiftySixth = 4, // 256 * 256 = 65k texels + Max = TwoHundredFiftySixth, + Error = 5 // Error. We don't want to go lower (GPU occupancy will start to be a problem for smaller atlas sizes). + }; + public struct LightmapRequest + { + public LightmapRequestOutputType outputTypeMask; + public UInt32 lightmapOffset; + public UInt32 lightmapCount; + public TilingMode tilingMode; + public string outputFolderPath; + }; + public struct Resolution + { + public Resolution(UInt32 widthIn, UInt32 heightIn) + { + width = widthIn; + height = heightIn; + } + public UInt32 width; + public UInt32 height; + } + + public struct TextureData + { + public TextureData(Resolution resolutionIn) + { + resolution = resolutionIn; + data = new Vector4[resolution.width * resolution.height]; + } + public Resolution resolution; + public Vector4[] data; + } + + public struct CookieData + { + public CookieData(Resolution resolutionIn, UInt32 pixelStrideIn, UInt32 slicesIn, bool repeatIn) + { + resolution = resolutionIn; + pixelStride = pixelStrideIn; + slices = slicesIn; + repeat = repeatIn; + data = new byte[resolution.width * resolution.height * slices* pixelStride]; + } + public Resolution resolution; + public UInt32 pixelStride; + public UInt32 slices; + public bool repeat; + public byte[] data; + } + + public struct Instance + { + public MeshType meshType; + public int meshIndex; // index into BakeInput::m_MeshData, -1 for Terrain + public int terrainIndex; // index into BakeInput::m_TerrainData, -1 for MeshRenderer + public Matrix4x4 transform; + public bool castShadows; + public bool receiveShadows; + public bool oddNegativeScale; + public int lodGroup; + public byte lodMask; + public int[] submeshMaterialIndices; + } + + public struct Terrain + { + public UInt32 heightMapIndex; // index into BakeInput::m_HeightmapData + public int terrainHoleIndex; // index into BakeInput::m_TerrainHoleData -1 means no hole data + public float outputResolution; + public Vector3 heightmapScale; + public Vector4 uvBounds; + } + + public struct Material + { + public bool doubleSidedGI; + public TransmissionChannels transmissionChannels; + public TransmissionType transmissionType; + } + + public enum LightType : byte + { + Directional = 0, + Point = 1, + Spot = 2, + Rectangle = 3, + Disc = 4, + SpotPyramidShape = 5, + SpotBoxShape = 6 + }; + + public enum FalloffType : byte + { + InverseSquared = 0, + InverseSquaredNoRangeAttenuation = 1, + Linear = 2, + Legacy = 3 + }; + + public enum AngularFalloffType : byte + { + LUT = 0, + AnalyticAndInnerAngle = 1 + }; + + public enum LightMode : byte + { + Realtime = 0, + Mixed = 1, + Baked = 2 + }; + + public struct Light + { + public Vector3 color; + public Vector3 indirectColor; + public Quaternion orientation; + public Vector3 position; + public float range; + public Int32 cookieTextureIndex; + public float cookieScale; + public float coneAngle; + public float innerConeAngle; + public float shape0; + public float shape1; + public LightType type; + public LightMode mode; + public FalloffType falloff; + public AngularFalloffType angularFalloff; + public bool castsShadows; + public Int32 shadowMaskChannel; + } + + public struct SampleCount + { + public UInt32 directSampleCount; + public UInt32 indirectSampleCount; + public UInt32 environmentSampleCount; + }; + + public struct LightingSettings + { + public SampleCount lightmapSampleCounts; + public SampleCount probeSampleCounts; + public UInt32 minBounces; + public UInt32 maxBounces; + public LightmapBakeMode lightmapBakeMode; + public MixedLightingMode mixedLightingMode; + public bool aoEnabled; + public float aoDistance; + }; + + [StructLayout(LayoutKind.Sequential)] + public class BakeInput : IDisposable + { + internal IntPtr m_Ptr; + internal bool m_OwnsPtr; + + public BakeInput() + { + m_Ptr = Internal_Create(); + m_OwnsPtr = true; + } + public BakeInput(IntPtr ptr) + { + m_Ptr = ptr; + m_OwnsPtr = false; + } + ~BakeInput() + { + Destroy(); + } + + public void Dispose() + { + Destroy(); + GC.SuppressFinalize(this); + } + + void Destroy() + { + if (m_OwnsPtr && m_Ptr != IntPtr.Zero) + { + Internal_Destroy(m_Ptr); + m_Ptr = IntPtr.Zero; + } + } + + extern public UInt64 GetByteSize(); + + public Texture2D GetAlbedoTexture(UInt32 index) + { + if (index >= albedoTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (albedoTextureCount - 1) + ", but was {0}", index)); + TextureData textureData = GetAlbedoTextureData(index); + var tex = new Texture2D((int)textureData.resolution.width, (int)textureData.resolution.height, TextureFormat.RGBAFloat, false); + tex.SetPixelData(textureData.data, 0); + tex.filterMode = FilterMode.Point; + tex.Apply(); + return tex; + } + + public Texture2D GetEmissiveTexture(UInt32 index) + { + if (index >= emissiveTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (emissiveTextureCount - 1) + ", but was {0}", index)); + TextureData textureData = GetEmissiveTextureData(index); + var tex = new Texture2D((int)textureData.resolution.width, (int)textureData.resolution.height, TextureFormat.RGBAFloat, false); + tex.SetPixelData(textureData.data, 0); + tex.filterMode = FilterMode.Point; + tex.Apply(); + return tex; + } + + static extern IntPtr Internal_Create(); + [NativeMethod(IsThreadSafe = true)] + static extern void Internal_Destroy(IntPtr ptr); + + extern LightingSettings Internal_GetLightingSettings(); + public LightingSettings GetLightingSettings() + { + return Internal_GetLightingSettings(); + } + extern void Internal_SetLightingSettings(LightingSettings lightingSettings); + public void SetLightingSettings(LightingSettings lightingSettings) + { + Internal_SetLightingSettings(lightingSettings); + } + + extern UInt32 Internal_LightmapWidth(UInt32 index); + extern UInt32 Internal_LightmapHeight(UInt32 index); + extern UInt32 Internal_InstanceCount(UInt32 lightmapIndex); + + extern public UInt32 instanceCount { get; } + extern Instance Internal_Instance(UInt32 index); + public Instance instance(UInt32 index) + { + if (index >= instanceCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (instanceCount - 1) + ", but was {0}", index)); + Instance instance = Internal_Instance(index); + return instance; + } + + extern public UInt32 terrainCount { get; } + extern Terrain Internal_GetTerrain(UInt32 index); + public Terrain GetTerrain(UInt32 index) + { + if (index >= terrainCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (terrainCount - 1) + ", but was {0}", index)); + Terrain terrain = Internal_GetTerrain(index); + return terrain; + } + + public UInt32 lightmapInstanceCount(UInt32 index) + { + if (index >= lightmapCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (lightmapCount - 1) + ", but was {0}", index)); + return Internal_InstanceCount(index); + } + + extern public UInt32 meshCount { get; } + extern public UInt32 heightmapCount { get; } + extern public UInt32 holemapCount { get; } + extern public UInt32 materialCount { get; } + extern Material Internal_GetMaterial(UInt32 index); + extern void Internal_SetMaterial(UInt32 index, Material material); + public Material GetMaterial(UInt32 index) + { + if (index >= materialCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (materialCount - 1) + ", but was {0}", index)); + Material material = Internal_GetMaterial(index); + return material; + } + public void SetMaterial(UInt32 index, Material material) + { + if (index >= materialCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (materialCount - 1) + ", but was {0}", index)); + Internal_SetMaterial(index, material); + } + public int GetMaterialIndex(UInt32 instanceIndex, UInt32 submeshIndex) + { + if (instanceIndex >= instanceCount) + throw new ArgumentException(string.Format("instanceIndex must be between 0 and " + (instanceCount - 1) + ", but was {0}", instanceIndex)); + Instance theInstance = instance(instanceIndex); + if (theInstance.submeshMaterialIndices.Length == 0) + throw new ArgumentException(string.Format("instance {0} has not materials", instanceIndex)); + if (submeshIndex >= theInstance.submeshMaterialIndices.Length) + throw new ArgumentException(string.Format("submeshIndex must be between 0 and " + (theInstance.submeshMaterialIndices.Length - 1) + ", but was {0}", submeshIndex)); + return theInstance.submeshMaterialIndices[submeshIndex]; + } + extern UInt32 Internal_GetLightCount(); + public UInt32 GetLightCount() + { + return Internal_GetLightCount(); + } + extern Light Internal_GetLight(UInt32 index); + extern void Internal_SetLight(UInt32 index, Light light); + public Light GetLight(UInt32 index) + { + if (index >= GetLightCount()) + throw new ArgumentException(string.Format("index must be between 0 and " + (GetLightCount() - 1) + ", but was {0}", index)); + return Internal_GetLight(index); + } + public void SetLight(UInt32 index, Light light) + { + if (index >= GetLightCount()) + throw new ArgumentException(string.Format("index must be between 0 and " + (GetLightCount() - 1) + ", but was {0}", index)); + Internal_SetLight(index, light); + } + + extern Int32 Internal_instanceAlbedoEmissiveIndex(UInt32 instanceIndex); + extern Int32 Internal_instanceTransmissiveIndex(UInt32 instanceIndex, UInt32 submeshIndex); + public Int32 instanceToAlbedoIndex(UInt32 instanceIndex) + { + if (instanceIndex >= instanceCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (instanceCount - 1) + ", but was {0}", instanceIndex)); + return Internal_instanceAlbedoEmissiveIndex(instanceIndex); + } + public Int32 instanceToEmissiveIndex(UInt32 instanceIndex) + { + if (instanceIndex >= instanceCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (instanceCount - 1) + ", but was {0}", instanceIndex)); + return Internal_instanceAlbedoEmissiveIndex(instanceIndex); + } + public Int32 instanceToTransmissiveIndex(UInt32 instanceIndex, UInt32 submeshIndex) + { + if (instanceIndex >= instanceCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (instanceCount - 1) + ", but was {0}", instanceIndex)); + Instance inst = instance(instanceIndex); + int submeshCount = inst.submeshMaterialIndices.Length; + if (submeshIndex >= submeshCount) + throw new ArgumentException(string.Format("submeshIndex must be between 0 and " + (submeshCount - 1) + ", but was {0}", submeshIndex)); + int materialIndex = inst.submeshMaterialIndices[submeshIndex]; + if (materialIndex == -1) + throw new ArgumentException(string.Format("material for submesh {0} did not exist.", submeshIndex)); + + return Internal_instanceTransmissiveIndex(instanceIndex, submeshIndex); + } + + extern public UInt32 albedoTextureCount { get; } + extern TextureData Internal_GetAlbedoTextureData(UInt32 index); + extern void Internal_SetAlbedoTextureData(UInt32 index, TextureData textureData); + + public TextureData GetAlbedoTextureData(UInt32 index) + { + if (index >= albedoTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (albedoTextureCount - 1) + ", but was {0}", index)); + return Internal_GetAlbedoTextureData(index); + } + public void SetAlbedoTextureData(UInt32 index, TextureData textureData) + { + if (index >= albedoTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (albedoTextureCount - 1) + ", but was {0}", index)); + Internal_SetAlbedoTextureData(index, textureData); + } + + extern public UInt32 emissiveTextureCount { get; } + extern public UInt32 transmissiveTextureCount { get; } + extern TextureData Internal_GetEmissiveTextureData(UInt32 index); + extern void Internal_SetEmissiveTextureData(UInt32 index, TextureData textureData); + public TextureData GetEmissiveTextureData(UInt32 index) + { + if (index >= emissiveTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (emissiveTextureCount - 1) + ", but was {0}", index)); + return Internal_GetEmissiveTextureData(index); + } + public void SetEmissiveTextureData(UInt32 index, TextureData textureData) + { + if (index >= emissiveTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (emissiveTextureCount - 1) + ", but was {0}", index)); + Internal_SetEmissiveTextureData(index, textureData); + } + + extern TextureData Internal_GetTransmissiveTextureData(UInt32 index); + extern void Internal_SetTransmissiveTextureData(UInt32 index, TextureData textureData); + public TextureData GetTransmissiveTextureData(UInt32 index) + { + if (index >= transmissiveTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (transmissiveTextureCount - 1) + ", but was {0}", index)); + return Internal_GetTransmissiveTextureData(index); + } + public void SetTransmissiveTextureData(UInt32 index, TextureData textureData) + { + if (index >= emissiveTextureCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (transmissiveTextureCount - 1) + ", but was {0}", index)); + Internal_SetTransmissiveTextureData(index, textureData); + } + + extern public UInt32 GetCookieCount(); + extern CookieData Internal_GetCookieData(UInt32 index); + extern void Internal_SetCookieData(UInt32 index, CookieData cookieData); + public CookieData GetCookieData(UInt32 index) + { + if (index >= GetCookieCount()) + throw new ArgumentException(string.Format("index must be between 0 and " + (GetCookieCount() - 1) + ", but was {0}", index)); + return Internal_GetCookieData(index); + } + public void SetCookieData(UInt32 index, CookieData cookieData) + { + if (index >= GetCookieCount()) + throw new ArgumentException(string.Format("index must be between 0 and " + (GetCookieCount() - 1) + ", but was {0}", index)); + Internal_SetCookieData(index, cookieData); + } + + extern public UInt32 lightmapCount { get; } + public Resolution lightmapResolution(UInt32 index) + { + if (index >= lightmapCount) + throw new ArgumentException(string.Format("index must be between 0 and " + (lightmapCount - 1) + ", but was {0}", index)); + Resolution resolution; + resolution.width = Internal_LightmapWidth(index); + resolution.height = Internal_LightmapHeight(index); + return resolution; + } + extern public UInt32 lightProbeCount { get; } + + extern public void SetLightmapResolution(Resolution resolution); + extern public void SetEnvironment(Vector4 color); + + extern public void SetBackend(Backend backend); + public extern ProbeRequest[] GetProbeRequests(); + public extern void SetLightProbeRequests(ProbeRequest[] requests); + public extern LightmapRequest[] GetLightmapRequests(); + public extern void SetLightmapRequests(LightmapRequest[] requests); + + private extern unsafe void SetProbePositions([Span("positionsLength", isReadOnly: true)] Vector3* positions, int positionsLength); + + public void SetProbePositions(Vector3[] positions) + { + SetProbePositions(positions.AsSpan()); + } + public unsafe void SetProbePositions(ReadOnlySpan positions) + { + fixed (Vector3* positionsPtr = positions) + { + SetProbePositions(positionsPtr, positions.Length); + } + } + public extern Vector3[] GetProbePositions(); + } + extern static public Result Bake(BakeInput input); + } +} diff --git a/Editor/Mono/GI/Lightmapping.bindings.cs b/Editor/Mono/GI/Lightmapping.bindings.cs index 2cd59f54c..c670e84a3 100644 --- a/Editor/Mono/GI/Lightmapping.bindings.cs +++ b/Editor/Mono/GI/Lightmapping.bindings.cs @@ -12,6 +12,7 @@ using Scene = UnityEngine.SceneManagement.Scene; using NativeArrayUnsafeUtility = Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility; using Unity.Collections; +using UnityEditor.LightBaking; using UnityEngine.Rendering; namespace UnityEditor @@ -217,41 +218,24 @@ internal static FilterMode filterMode set { GetOrCreateLightingsSettings().lightmapFilterMode = value; } } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern bool isProgressiveLightmapperDone {[NativeName("IsDone")] get; } + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] + internal static extern bool isProgressiveLightmapperDone {[NativeName("IsBakedGIDone")] get; } - // Returns true when the bake job is baking, false otherwise. - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern bool isBaking { [NativeName("IsBaking")] get; } - - // Returns true when the bake job is preparing, false otherwise. - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern bool isPreparing { [NativeName("IsPreparing")] get; } - - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern ulong occupiedTexelCount { get; } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern ulong GetVisibleTexelCount(int lightmapIndex); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern int atlasCount { [NativeName("GetAtlasCount")] get; } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern LightmapConvergence GetLightmapConvergence(int lightmapIndex); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern LightProbesConvergence GetLightProbesConvergence(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern LightmapMemory GetLightmapMemory(int lightmapIndex); - - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern bool GetGBufferHash(int lightmapIndex, out Hash128 gbufferHash); - - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern float GetGBufferMemory(ref Hash128 gbufferHash); - [FreeFunction] internal static extern MemLabels GetLightProbeMemLabels(); @@ -271,36 +255,33 @@ internal static FilterMode filterMode [NativeName("GetGeometryMemory")] internal static extern GeoMemLabels GetGeometryMemory(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern float ComputeTotalCPUMemoryUsageInBytes(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern float ComputeTotalGPUMemoryUsageInBytes(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern void LogGPUMemoryStatistics(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern float GetLightmapBakeTimeRaw(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern float GetLightmapBakeTimeTotal(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] [NativeName("GetLightmapBakePerformance")] internal static extern float GetLightmapBakePerformanceTotal(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - internal static extern float GetLightmapBakePerformance(int lightmapIndex); - - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern string GetLightmapBakeGPUDeviceName(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] internal static extern int GetLightmapBakeGPUDeviceIndex(); - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - extern internal static DeviceAndPlatform[] GetLightmappingGpuDevices(); + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] + internal static extern DeviceAndPlatform[] GetLightmappingGpuDevices(); // Exports the current state of the scene to the dynamic GI workflow. [FreeFunction] @@ -320,6 +301,7 @@ internal static FilterMode filterMode // Stops the current bake at the state it has reached so far. [FreeFunction] + [System.Obsolete("ForceStop is no longer available, use Cancel instead to stop a bake.", false)] public static extern void ForceStop(); // Returns true when the bake job is running, false otherwise (RO). @@ -411,6 +393,16 @@ internal static void Internal_CallOnWroteLightingDataAsset() wroteLightingDataAsset(); } + // This event is fired when BakeInput has been populated, but before passing it to Bake(). + // Create a LightBaker.BakeInput by passing the IntPtr but don't access it beyond the call-back. + internal static event Action createdBakeInputForTestingOnly; + + internal static void Internal_CallOnCreatedBakeInputForTestingOnly(IntPtr ptr) + { + if (createdBakeInputForTestingOnly != null) + createdBakeInputForTestingOnly(ptr); + } + [System.Obsolete("OnCompletedFunction.completed is obsolete, please use event bakeCompleted instead. ", false)] public static OnCompletedFunction completed; @@ -458,17 +450,6 @@ public static void Tetrahedralize(Vector3[] positions, out int[] outIndices, out [FreeFunction] private static extern TetrahedralizationData TetrahedralizeInternal(Vector3[] positions); - internal static void GetEnvironmentSamples(out Vector4[] outDirections, out Vector4[] outIntensities) - { - EnvironmentSamplesData data = GetEnvironmentSamplesInternal(); - outDirections = data.directions; - outIntensities = data.intensities; - } - - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - [NativeName("GetEnvironmentSamples")] - private static extern EnvironmentSamplesData GetEnvironmentSamplesInternal(); - [FreeFunction] public static extern bool BakeReflectionProbe(ReflectionProbe probe, string path); @@ -680,18 +661,6 @@ public static void BakeMultipleScenes(string[] paths) [NativeHeader("Editor/Src/GI/EditorHelpers.h")] extern static internal bool GetInstanceHash([NotNull("NullExceptionObject")] Renderer renderer, out Hash128 instanceHash); - [FreeFunction] - [NativeHeader("Editor/Src/GI/EditorHelpers.h")] - extern static internal bool GetPVRInstanceHash(int instanceID, out Hash128 instanceHash); - - [FreeFunction] - [NativeHeader("Editor/Src/GI/EditorHelpers.h")] - extern static internal bool GetPVRAtlasHash(int instanceID, out Hash128 atlasHash); - - [FreeFunction] - [NativeHeader("Editor/Src/GI/EditorHelpers.h")] - extern static internal bool GetPVRAtlasInstanceOffset(int instanceID, out int atlasInstanceOffset); - [FreeFunction] [NativeHeader("Editor/Src/GI/EditorHelpers.h")] extern static internal bool GetGeometryHash([NotNull("NullExceptionObject")] Renderer renderer, out Hash128 geometryHash); @@ -706,11 +675,11 @@ namespace UnityEditor.Experimental { public sealed partial class Lightmapping { - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] public static extern bool probesIgnoreDirectEnvironment { get; set; } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - private unsafe static extern void SetCustomBakeInputs([Span("inputDataLength", isReadOnly:true)]Vector4* inputData, int inputDataLength, int sampleCount); + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] + private static extern unsafe void SetCustomBakeInputs([Span("inputDataLength", isReadOnly:true)]Vector4* inputData, int inputDataLength, int sampleCount); public static void SetCustomBakeInputs(Vector4[] inputData, int sampleCount) { @@ -725,8 +694,8 @@ public static unsafe void SetCustomBakeInputs(ReadOnlySpan inputData, i } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] - private static unsafe extern bool GetCustomBakeResultsCopy([Span("resultsLength")]Vector4* results, int resultsLength); + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] + private static extern unsafe bool GetCustomBakeResultsCopy([Span("resultsLength")]Vector4* results, int resultsLength); public static unsafe bool GetCustomBakeResults(Span results) { fixed (Vector4* resultsPtr = results) { @@ -738,7 +707,7 @@ public static bool GetCustomBakeResults([Out] Vector4[] results) return GetCustomBakeResults(results.AsSpan()); } - [StaticAccessor("ProgressiveRuntimeManager::Get()", StaticAccessorType.Arrow)] + [StaticAccessor("BakedGISceneManager::Get()", StaticAccessorType.Arrow)] public static extern ReadOnlySpan GetCustomBakeResultsNoCopy(); [Obsolete("UnityEditor.Experimental.Lightmapping.extractAmbientOcclusion is obsolete, use LightingSettings.extractAO instead. ", false)] diff --git a/Editor/Mono/GUI/DockArea.cs b/Editor/Mono/GUI/DockArea.cs index 65bea38bf..c5dd29f5b 100644 --- a/Editor/Mono/GUI/DockArea.cs +++ b/Editor/Mono/GUI/DockArea.cs @@ -138,7 +138,7 @@ protected override void OnDestroy() m_IsBeingDestroyed = true; if (hasFocus) - m_OnLostFocus?.Invoke(); + OnLostFocus(); actualView = null; @@ -248,8 +248,14 @@ public void RemoveTab(EditorWindow pane, bool killIfEmpty, bool sendEvents = tru private static void UpdateWindowTitle(EditorWindow w) { - if (w && w.m_Parent && w.m_Parent.window && w.titleContent != null) + if (w && + w.m_Parent && + w.m_Parent.window && + !w.m_Parent.window.m_IsMppmCloneWindow && + w.titleContent != null) + { w.m_Parent.window.title = w.titleContent.text; + } } private static void UpdateWindowHasUnsavedChanges(EditorWindow w) @@ -855,8 +861,8 @@ private float DragTab(Rect tabAreaRect, float scrollOffset, GUIStyle tabStyle, G if (ModeService.HasCapability(ModeCapability.StaticTabs, false)) { - break; - } + break; + } if (evt.pointerType == PointerType.Pen && !evt.penStatus.HasFlag(PenStatus.Contact)) break; diff --git a/Editor/Mono/GUI/LazyLoadReferenceField.cs b/Editor/Mono/GUI/LazyLoadReferenceField.cs index 31fed6aff..83e7b5dc1 100644 --- a/Editor/Mono/GUI/LazyLoadReferenceField.cs +++ b/Editor/Mono/GUI/LazyLoadReferenceField.cs @@ -35,7 +35,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType); - var objectField = new UnityEditor.UIElements.ObjectField(property.displayName); + var objectField = new UnityEditor.UIElements.ObjectField(preferredLabel); var genericType = fieldType.GetGenericArguments()[0]; objectField.objectType = genericType; objectField.value = property.objectReferenceValue; diff --git a/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs b/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs index 88b56addc..adb1ff597 100644 --- a/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs +++ b/Editor/Mono/GUI/Toolbars/Core/EditorToolbar.cs @@ -16,6 +16,7 @@ sealed class EditorToolbar public const string elementClassName = "unity-editor-toolbar-element"; public const string elementIconClassName = elementClassName + "__icon"; public const string elementLabelClassName = elementClassName + "__label"; + public const string elementTextIconClassName = elementClassName + "__text-icon"; string[] m_ToolbarElements; readonly EditorWindow m_Context; diff --git a/Editor/Mono/GUI/Tools/BuiltinTools.cs b/Editor/Mono/GUI/Tools/BuiltinTools.cs index 8b29d4ff5..5541d414f 100644 --- a/Editor/Mono/GUI/Tools/BuiltinTools.cs +++ b/Editor/Mono/GUI/Tools/BuiltinTools.cs @@ -591,7 +591,7 @@ protected override void ToolGUI(SceneView view, Vector3 handlePosition, bool isS TransformManipulator.BeginManipulationHandling(false); // Move handle EditorGUI.BeginChangeCheck(); - Vector3 newPos = MoveHandlesGUI(rect, handlePosition, rectRotation); + Vector3 newPos = MoveHandlesGUI(rect, rectRotation * rect.center + handlePosition, rectRotation); if (EditorGUI.EndChangeCheck() && !isStatic) { if (GridSnapping.active) diff --git a/Editor/Mono/GUI/Tools/EditorToolGUI.cs b/Editor/Mono/GUI/Tools/EditorToolGUI.cs index 9907f9315..4a64680cd 100644 --- a/Editor/Mono/GUI/Tools/EditorToolGUI.cs +++ b/Editor/Mono/GUI/Tools/EditorToolGUI.cs @@ -24,6 +24,40 @@ public sealed partial class EditorGUILayout static class Styles { public static GUIStyle command = "AppCommand"; + public static GUIStyle commandLeft; + public static GUIStyle commandMid; + public static GUIStyle commandRight; + + static Styles() + { + GUI.FindStyles(ref command, out commandLeft, out commandMid, out commandRight, "left", "mid", "right"); + + var normalColor = command.normal.textColor; + + command.imagePosition = ImagePosition.ImageAbove; + command.active.textColor = normalColor; + command.onActive.textColor = normalColor; + command.onNormal.textColor = normalColor; + command.fontStyle = FontStyle.Bold; + + commandLeft.imagePosition = ImagePosition.ImageAbove; + commandLeft.active.textColor = normalColor; + commandLeft.onActive.textColor = normalColor; + commandLeft.onNormal.textColor = normalColor; + commandLeft.fontStyle = FontStyle.Bold; + + commandMid.imagePosition = ImagePosition.ImageAbove; + commandMid.active.textColor = normalColor; + commandMid.onActive.textColor = normalColor; + commandMid.onNormal.textColor = normalColor; + commandMid.fontStyle = FontStyle.Bold; + + commandRight.imagePosition = ImagePosition.ImageAbove; + commandRight.active.textColor = normalColor; + commandRight.onActive.textColor = normalColor; + commandRight.onNormal.textColor = normalColor; + commandRight.fontStyle = FontStyle.Bold; + } } public static void EditorToolbarForTarget(UObject target) @@ -138,7 +172,7 @@ internal static bool EditorToolbar(GUIContent content, T selected, IList t EditorGUI.BeginChangeCheck(); - index = GUILayout.Toolbar(index, buttons, enabled, Styles.command); + index = GUILayout.Toolbar(index, buttons, enabled, Styles.command, Styles.commandLeft, Styles.commandMid, Styles.commandRight); if (EditorGUI.EndChangeCheck()) { diff --git a/Editor/Mono/GUI/Tools/EditorToolUtility.cs b/Editor/Mono/GUI/Tools/EditorToolUtility.cs index b9b1a131e..a925faac8 100644 --- a/Editor/Mono/GUI/Tools/EditorToolUtility.cs +++ b/Editor/Mono/GUI/Tools/EditorToolUtility.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using UnityEditor.Overlays; using UnityEngine; using UObject = UnityEngine.Object; @@ -212,8 +213,10 @@ internal static bool IsGlobalTool(EditorTool tool) if(GetEnumWithEditorTool(tool) == Tool.Custom) { var type = tool.GetType(); - return !IsComponentTool(type) - && EditorToolManager.additionalContextToolTypesCache.All(t => t != type); + return !IsComponentTool(type) // Component tool? + && !IsManipulationTool(GetEnumWithEditorTool(tool, EditorToolManager.GetSingleton())) // Built-in tool? + && !IsBuiltinOverride(tool) // Built-in tool override? + && EditorToolManager.additionalContextToolTypesCache.Any(t => t == type); // Additional/Extra tool? } return false; @@ -249,13 +252,8 @@ internal static GUIContent GetIcon(Type editorToolType, bool forceReload = false if(( res.image = EditorGUIUtility.FindTexture(editorToolType) ) != null) goto ReturnToolbarIcon; - // If it's a custom editor tool, try to get an icon for the tool's target type - var attrib = GetEditorToolAttribute(editorToolType); - if(attrib?.targetType != null && ( res.image = AssetPreview.GetMiniTypeThumbnailFromType(attrib.targetType) ) != null) - goto ReturnToolbarIcon; - - // And finally fall back to the default Custom Tool icon - res.image = EditorGUIUtility.IconContent("CustomTool").image; + // And finally fall back to the significant letters of the tool's typename + res.text = OverlayUtilities.GetSignificantLettersForIcon(editorToolType.Name); ReturnToolbarIcon: if (string.IsNullOrEmpty(res.tooltip)) diff --git a/Editor/Mono/GUI/Tools/GameObjectToolContext.cs b/Editor/Mono/GUI/Tools/GameObjectToolContext.cs index e30582f40..d9b982289 100644 --- a/Editor/Mono/GUI/Tools/GameObjectToolContext.cs +++ b/Editor/Mono/GUI/Tools/GameObjectToolContext.cs @@ -9,7 +9,7 @@ namespace UnityEditor.EditorTools [EditorToolContext, Icon(k_IconPath)] public sealed class GameObjectToolContext : EditorToolContext { - const string k_IconPath = "Toolbars/ObjectMode"; + const string k_IconPath = "GameObject Icon"; GameObjectToolContext() {} } } diff --git a/Editor/Mono/GUI/WindowLayout.cs b/Editor/Mono/GUI/WindowLayout.cs index 9e1967aa7..e08faf11f 100644 --- a/Editor/Mono/GUI/WindowLayout.cs +++ b/Editor/Mono/GUI/WindowLayout.cs @@ -82,8 +82,10 @@ public float size internal static string layoutsPreferencesPath => FileUtil.CombinePaths(InternalEditorUtility.unityPreferencesFolder, "Layouts"); internal static string layoutsModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, ModeService.currentId); internal static string layoutsDefaultModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, "default"); + internal static string layoutsCurrentModePreferencesPath => FileUtil.CombinePaths(layoutsPreferencesPath, "current"); internal static string layoutsProjectPath => FileUtil.CombinePaths("UserSettings", "Layouts"); internal static string ProjectLayoutPath => GetProjectLayoutPerMode(ModeService.currentId); + internal static string currentLayoutName => GetLayoutFileName(ModeService.currentId, Application.unityVersionVer); [UsedImplicitly, RequiredByNativeCode] public static void LoadDefaultWindowPreferences() @@ -97,14 +99,13 @@ public static void LoadCurrentModeLayout(bool keepMainWindow) InitializeLayoutPreferencesFolder(); var dynamicLayout = ModeService.GetDynamicLayout(); if (dynamicLayout == null) - LoadProjectLayout(keepMainWindow); + LoadLastUsedLayoutForCurrentMode(keepMainWindow); else { - var projectLayoutExists = File.Exists(ProjectLayoutPath); if ((projectLayoutExists && Convert.ToBoolean(dynamicLayout["restore_saved_layout"])) || !LoadModeDynamicLayout(keepMainWindow, dynamicLayout)) - LoadProjectLayout(keepMainWindow); + LoadLastUsedLayoutForCurrentMode(keepMainWindow); } } @@ -210,7 +211,7 @@ internal static void InitContainerWindow(ref ContainerWindow window, string wind { if (window == null) { - + window = ScriptableObject.CreateInstance(); var windowMinSize = new Vector2(120, 80); @@ -283,7 +284,7 @@ internal static ContainerWindow ShowWindowWithDynamicLayout(string windowId, str var window = Resources.FindObjectsOfTypeAll().FirstOrDefault(w => w.windowID == windowId); InitContainerWindow(ref window, windowId, layoutData); - + window.m_IsMppmCloneWindow = true; GenerateLayout(window, ShowMode.Utility, availableEditorWindowTypes, centerViewInfo, topViewInfo, bottomViewInfo, layoutData); return window; } @@ -413,23 +414,73 @@ private static bool ParseViewData(Type[] availableEditorWindowTypes, object view return true; } - private static void LoadProjectLayout(bool keepMainWindow) + // Used by tests + internal static string GetLayoutFileName(string mode, int version) => $"{mode}-{version}.dwlt"; + + static IEnumerable GetCurrentModeLayouts() { - var projectLayoutExists = File.Exists(ProjectLayoutPath); - if (!projectLayoutExists) + var layouts = ModeService.GetModeDataSection(ModeService.currentIndex, ModeDescriptor.LayoutsKey); + + if (layouts is IList modeLayoutPaths) { - var currentLayoutPath = GetCurrentLayoutPath(); - if (EnsureDirectoryCreated(ProjectLayoutPath)) + foreach (var layoutPath in modeLayoutPaths.Cast()) { - Console.WriteLine($"[LAYOUT] LoadProjectLayout: Copying Project Current Layout: {ProjectLayoutPath} from {currentLayoutPath}"); - FileUtil.CopyFileOrDirectory(currentLayoutPath, ProjectLayoutPath); + if (!File.Exists(layoutPath)) + continue; + yield return layoutPath; } } + } - Debug.Assert(File.Exists(ProjectLayoutPath)); + // Iterate through potential layouts in descending order of precedence. + // 1. Last loaded layout in project for matching Unity version + // 2. Last loaded layout in project for any Unity version, in descending alphabetical order + // 3. Last loaded layout in global preferences for matching Unity version + // 4. Last loaded layout in global preferences for any Unity version, in descending alphabetical order + // 5. Any available layouts specified by the EditorMode, if EditorMode supplies layouts + // 6. The factory default layout + private static void LoadLastUsedLayoutForCurrentMode(bool keepMainWindow) + { + // steps 1-4 + foreach (var layout in GetLastLayout()) + if (LoadWindowLayout(layout, layout != ProjectLayoutPath, false, keepMainWindow)) + return; + + // step 5 + foreach (var layout in GetCurrentModeLayouts()) + if (LoadWindowLayout(layout, layout != ProjectLayoutPath, false, keepMainWindow)) + return; + + // step 6 + var defaultLayout = Path.Combine(layoutsDefaultModePreferencesPath, kDefaultLayoutName); + + // If all else fails, load the default layout that ships with the editor. If that fails, prompt the user to + // restore the default layouts. + if (!LoadWindowLayout(defaultLayout, true, false, keepMainWindow)) + { + int option = 0; + + if (!Application.isTestRun && Application.isHumanControllingUs) + { + option = EditorUtility.DisplayDialogComplex("Missing Default Layout", "No valid user created or " + + "default window layout found. Please revert factory settings to restore the default layouts.", + "Quit", "Revert Factory Settings", ""); + } + else + { + ResetUserLayouts(); + } - // Load the current project layout - LoadWindowLayout(ProjectLayoutPath, !projectLayoutExists, false, keepMainWindow); + switch (option) + { + case 0: + EditorApplication.Exit(0); + break; + case 1: + ResetFactorySettings(); + break; + } + } } [UsedImplicitly, RequiredByNativeCode] @@ -444,8 +495,49 @@ public static void SaveDefaultWindowPreferences() internal static void SaveCurrentLayoutPerMode(string modeId) { - // Save Project Current Layout + // Save the layout in two places. Once in the Project/UserSettings directory, then again the global + // preferences. The latter is used when opening a new project (or any case where UserSettings/Layouts/ does + // not exist). SaveWindowLayout(FileUtil.CombinePaths(Directory.GetCurrentDirectory(), GetProjectLayoutPerMode(modeId))); + SaveWindowLayout(Path.Combine(layoutsCurrentModePreferencesPath, GetLayoutFileName(modeId, Application.unityVersionVer))); + } + + // Iterate through potential layout files, prioritizing exact match followed by descending unity version. + // IMPORTANT: This function is "dumb" in that it does not do any kind of sophisticated version comparison. If the + // naming scheme for current layouts is changed, or this function is called on to sort user saved layouts, you will + // need to add more sophisticated filtering. + public static IEnumerable GetLastLayout(string directory, string mode, int version) + { + var currentModeAndVersionLayout = GetLayoutFileName(mode, version); + string layoutSearchPattern = $"{mode}-*.*wlt"; + + // first try the exact match + var preferred = Path.Combine(directory, currentModeAndVersionLayout); + + if(File.Exists(preferred)) + yield return preferred; + + // if that fails, fall back to layouts for this mode from other unity versions in descending order + if (Directory.Exists(directory)) + { + var paths = Directory.GetFiles(directory, layoutSearchPattern) + .Where(p => string.Compare(p, preferred, StringComparison.OrdinalIgnoreCase) != 0) + .OrderByDescending(p => p, StringComparer.OrdinalIgnoreCase); + + foreach (var path in paths) + yield return path; + } + } + + // used by Tests/EditModeAndPlayModeTests/EditorModes + internal static IEnumerable GetLastLayout() + { + var mode = ModeService.currentId; + var version = Application.unityVersionVer; + foreach (var layout in GetLastLayout(layoutsProjectPath, mode, version)) + yield return layout; + foreach (var layout in GetLastLayout(layoutsCurrentModePreferencesPath, mode, version)) + yield return layout; } internal static string GetCurrentLayoutPath() @@ -466,7 +558,7 @@ internal static string GetDefaultLayoutPath() internal static string GetProjectLayoutPerMode(string modeId) { - return FileUtil.CombinePaths(layoutsProjectPath, $"{modeId}-{Application.unityVersionVer}.dwlt"); + return FileUtil.CombinePaths(layoutsProjectPath, GetLayoutFileName(modeId, Application.unityVersionVer)); } private static void InitializeLayoutPreferencesFolder() @@ -1248,7 +1340,7 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated UnityObject[] loadedWindows = InternalEditorUtility.LoadSerializedFileAndForget(path); if (loadedWindows == null || loadedWindows.Length == 0) - throw new LayoutException($"Window layout at {path} could not be loaded."); + throw new LayoutException("No windows found in layout."); List newWindows = new List(); @@ -1264,8 +1356,8 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated { if (!editorWin || !editorWin.m_Parent || !editorWin.m_Parent.window) { - Console.WriteLine("[LAYOUT] Removed unparented EditorWindow while reading window layout: window #" + i + ", type=" + - o.GetType() + ", instanceID=" + o.GetInstanceID()); + Console.WriteLine($"[LAYOUT] Removed un-parented EditorWindow while reading window layout" + + $" window #{i}, type={o.GetType()} instanceID={o.GetInstanceID()}"); UnityObject.DestroyImmediate(editorWin, true); layoutLoadingIssue = true; continue; @@ -1366,40 +1458,14 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated containerWindow.Show(containerWindow.showMode, loadPosition: false, displayImmediately: true, setFocus: true); } - // Unmaximize maximized PlayModeView window if maximize on play is enabled + // Un-maximize maximized PlayModeView window if maximize on play is enabled PlayModeView playModeView = GetMaximizedWindow() as PlayModeView; if (playModeView != null && playModeView.enterPlayModeBehavior == PlayModeView.EnterPlayModeBehavior.PlayMaximized) Unmaximize(playModeView); } catch (Exception ex) { - Debug.LogError("Failed to load window layout: " + ex); - - int option = 0; - if (!Application.isTestRun && Application.isHumanControllingUs) - { - option = EditorUtility.DisplayDialogComplex("Failed to load window layout", - $"This can happen if layout contains custom windows and there are compile errors in the project.\r\n\r\n{ex.Message}", - "Load Default Layout", "Quit", "Revert Factory Settings"); - } - else - { - ResetUserLayouts(); - } - - switch (option) - { - case 0: - LoadDefaultLayout(); - break; - case 1: - EditorApplication.Exit(0); - break; - case 2: - ResetFactorySettings(); - break; - } - + Debug.LogError($"Failed to load window layout \"{path}\": {ex}"); return false; } finally @@ -1413,7 +1479,7 @@ public static bool LoadWindowLayout(string path, bool newProjectLayoutWasCreated } if (layoutLoadingIssue) - Debug.Log("The editor layout could not be fully loaded, this can happen when the layout contains EditorWindows not available in this project"); + Debug.LogWarning($"The layout \"{path}\" could not be fully loaded, this can happen when the layout contains EditorWindows not available in this project."); return true; } @@ -1788,7 +1854,7 @@ internal static WindowFocusState instance get { if (m_Instance == null) - m_Instance = FindObjectOfType(typeof(WindowFocusState)) as WindowFocusState; + m_Instance = FindFirstObjectByType(typeof(WindowFocusState)) as WindowFocusState; if (m_Instance == null) m_Instance = CreateInstance(); return m_Instance; diff --git a/Editor/Mono/GUIView.cs b/Editor/Mono/GUIView.cs index a1c000a81..5b4061426 100644 --- a/Editor/Mono/GUIView.cs +++ b/Editor/Mono/GUIView.cs @@ -302,5 +302,17 @@ internal void CaptureMetalScene() System.Diagnostics.Process.Start(System.IO.Path.GetDirectoryName(path)); } } + + static readonly Action k_QueryMarkDirty = MarkDirtyRepaint; + + static void MarkDirtyRepaint(TextElement v) + { + v.MarkDirtyRepaint(); + } + + internal void RepaintUITKText() + { + visualTree.Query().ForEach(k_QueryMarkDirty); + } } } //namespace diff --git a/Editor/Mono/HostView.cs b/Editor/Mono/HostView.cs index 88baaf4e6..0433fead7 100644 --- a/Editor/Mono/HostView.cs +++ b/Editor/Mono/HostView.cs @@ -31,13 +31,31 @@ public static class DataModes { public const float switchButtonWidth = 16.0f; + static readonly string k_AutomaticAuthoringString = L10n.Tr("Automatic (Authoring)"); + static readonly string k_AutomaticMixedString = L10n.Tr("Automatic (Mixed)"); + static readonly string k_AutomaticRuntimeString = L10n.Tr("Automatic (Runtime)"); + static readonly string k_AuthoringString = L10n.Tr("Authoring"); + static readonly string k_MixedString = L10n.Tr("Mixed"); + static readonly string k_RuntimeString = L10n.Tr("Runtime"); + static readonly string k_DisabledString = L10n.Tr("Disabled"); + + // Auto data mode icons static readonly Texture2D k_AuthoringModeIcon = EditorGUIUtility.LoadIcon("DataMode.Authoring"); static readonly Texture2D k_MixedModeIcon = EditorGUIUtility.LoadIcon("DataMode.Mixed"); static readonly Texture2D k_RuntimeModeIcon = EditorGUIUtility.LoadIcon("DataMode.Runtime"); - public static readonly GUIContent authoringModeContent = EditorGUIUtility.TrIconContent(k_AuthoringModeIcon, "Data Mode: Authoring"); - public static readonly GUIContent mixedModeContent = EditorGUIUtility.TrIconContent(k_MixedModeIcon, "Data Mode: Mixed"); - public static readonly GUIContent runtimeModeContent = EditorGUIUtility.TrIconContent(k_RuntimeModeIcon, "Data Mode: Runtime"); + public static readonly GUIContent authoringModeContent = EditorGUIUtility.TrIconContent(k_AuthoringModeIcon, k_AutomaticAuthoringString); + public static readonly GUIContent mixedModeContent = EditorGUIUtility.TrIconContent(k_MixedModeIcon, k_AutomaticMixedString); + public static readonly GUIContent runtimeModeContent = EditorGUIUtility.TrIconContent(k_RuntimeModeIcon, k_AutomaticRuntimeString); + + // Sticky data mode icons + static readonly Texture2D k_StickyAuthoringModeIcon = EditorGUIUtility.LoadIcon("DataMode.Authoring.Sticky"); + static readonly Texture2D k_StickyMixedModeIcon = EditorGUIUtility.LoadIcon("DataMode.Mixed.Sticky"); + static readonly Texture2D k_StickyRuntimeModeIcon = EditorGUIUtility.LoadIcon("DataMode.Runtime.Sticky"); + + public static readonly GUIContent stickyAuthoringModeContent = EditorGUIUtility.TrIconContent(k_StickyAuthoringModeIcon, k_AuthoringString); + public static readonly GUIContent stickyMixedModeContent = EditorGUIUtility.TrIconContent(k_StickyMixedModeIcon, k_MixedString); + public static readonly GUIContent stickyRuntimeModeContent = EditorGUIUtility.TrIconContent(k_StickyRuntimeModeIcon, k_RuntimeString); // Use an empty style to avoid the hover effect of normal buttons public static readonly GUIStyle switchStyle = new GUIStyle(); @@ -45,10 +63,10 @@ public static class DataModes public static readonly Dictionary dataModeNameLabels = new Dictionary { - { DataMode.Disabled, EditorGUIUtility.TrTextContent("Disabled") }, - { DataMode.Authoring, EditorGUIUtility.TrTextContent("Authoring Mode") }, - { DataMode.Mixed, EditorGUIUtility.TrTextContent("Mixed Mode") }, - { DataMode.Runtime, EditorGUIUtility.TrTextContent("Runtime Mode") } + { DataMode.Disabled, EditorGUIUtility.TextContent(k_DisabledString) }, + { DataMode.Authoring, EditorGUIUtility.TextContent(k_AuthoringString) }, + { DataMode.Mixed, EditorGUIUtility.TextContent(k_MixedString) }, + { DataMode.Runtime, EditorGUIUtility.TextContent(k_RuntimeString) } }; } @@ -85,6 +103,8 @@ static Styles() protected EditorWindowDelegate m_ModifierKeysChanged; protected EditorWindowShowButtonDelegate m_ShowButton; + private bool m_IsLosingFocus = false; + internal EditorWindow actualView { get { return m_ActualView; } @@ -321,8 +341,22 @@ protected override bool OnFocus() internal void OnLostFocus() { + // Avoid calling OnLostFocus script callback if we are already processing the focus lost. + // This can happen when user scripts call Close() in OnLostFocus() callback. + if (m_IsLosingFocus) + return; + EditorGUI.EndEditingActiveTextField(); - m_OnLostFocus?.Invoke(); + + try + { + m_IsLosingFocus = true; + m_OnLostFocus?.Invoke(); + } + finally + { + m_IsLosingFocus = false; + } // Callback could have killed us if (!this) @@ -527,9 +561,6 @@ public IEditorWindowBackend editorWindowBackend set { windowBackend = value; } } - DataMode m_CachedDataMode; - bool m_ShouldDrawDataModeSwitch; - protected void RegisterSelectedPane(bool sendEvents) { if (!m_ActualView) @@ -561,19 +592,6 @@ protected void RegisterSelectedPane(bool sendEvents) EditorApplication.update += m_ActualView.CheckForWindowRepaint; } - if (m_ActualView is IDataModeHandler dataModeHandler) - { - UpdateDataMode(dataModeHandler.dataMode, false); - - if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) - dataModesDispatcher.dataModeChanged += OnViewDataModeChanged; - } - else - { - m_CachedDataMode = DataMode.Disabled; - m_ShouldDrawDataModeSwitch = false; - } - if (sendEvents) { try @@ -611,19 +629,15 @@ protected void DeregisterSelectedPane(bool clearActualView, bool sendEvents) EditorApplication.update -= m_ActualView.CheckForWindowRepaint; } - if (m_ActualView is IDataModeHandlerAndDispatcher dataModesDispatcher) - dataModesDispatcher.dataModeChanged -= OnViewDataModeChanged; - if (clearActualView) { - var onLostFocus = m_OnLostFocus; var onBecameInvisible = m_OnBecameInvisible; m_ActualView = null; if (sendEvents) { - onLostFocus?.Invoke(); + OnLostFocus(); onBecameInvisible?.Invoke(); } ClearDelegates(); @@ -696,7 +710,7 @@ internal float GetExtraButtonsWidth() if (m_ShowButton != null) extraWidth += ContainerWindow.kButtonWidth; - if (m_ShouldDrawDataModeSwitch) + if (m_ActualView.GetDataModeController_Internal().ShouldDrawDataModesSwitch()) extraWidth += Styles.DataModes.switchButtonWidth + Styles.iconMargin; foreach (var item in windowActions) @@ -723,13 +737,15 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) if (m_ShowButton != null) m_ShowButton.Invoke(new Rect(leftOffset, topOffset, ContainerWindow.kButtonWidth, ContainerWindow.kButtonHeight)); - if (m_ShouldDrawDataModeSwitch) + var dataModeControllerInternal = m_ActualView.GetDataModeController_Internal(); + if (dataModeControllerInternal.ShouldDrawDataModesSwitch()) { - var switchContent = m_CachedDataMode switch + var isClientInAutomaticDataMode = dataModeControllerInternal.isAutomatic; + var switchContent = dataModeControllerInternal.dataMode switch { - DataMode.Authoring => Styles.DataModes.authoringModeContent, - DataMode.Mixed => Styles.DataModes.mixedModeContent, - DataMode.Runtime => Styles.DataModes.runtimeModeContent, + DataMode.Authoring => isClientInAutomaticDataMode? Styles.DataModes.authoringModeContent : Styles.DataModes.stickyAuthoringModeContent, + DataMode.Mixed => isClientInAutomaticDataMode? Styles.DataModes.mixedModeContent : Styles.DataModes.stickyMixedModeContent, + DataMode.Runtime => isClientInAutomaticDataMode? Styles.DataModes.runtimeModeContent : Styles.DataModes.stickyRuntimeModeContent, _ => default }; @@ -741,11 +757,14 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) if (EditorGUI.Button(switchRect, switchContent, Styles.DataModes.switchStyle)) { - // This cast is guaranteed to work by m_ShouldDrawDataModeSwitch - var dataModesClient = (IDataModeHandler) m_ActualView; + var evt = Event.current; - dataModesClient.SwitchToNextDataMode(); - UpdateDataMode(dataModesClient.dataMode, true); + if (evt.button == 0 || evt.button == 1) // left click or right click + { + var menu = new GenericMenu(); + PopulateDataModeDropdown(dataModeControllerInternal, menu); + menu.DropDown(switchRect); + } } } } @@ -770,39 +789,23 @@ protected void ShowGenericMenu(float leftOffset, float topOffset) } } - bool ShouldDrawDataModesSwitch() + void PopulateDataModeDropdown(DataModeController clientDataModeController, GenericMenu menu) { - return m_ActualView is IDataModeHandler dataModesHandler - && dataModesHandler.dataMode != DataMode.Disabled - // We don't want to show this switch if there are not - // at least 2 modes supported at the current moment. - && dataModesHandler.supportedDataModes.Count > 1; - } + var autoLabel = !clientDataModeController.isAutomatic + ? L10n.Tr($"Automatic ({clientDataModeController.preferredDataMode})") + : L10n.Tr($"Automatic ({clientDataModeController.dataMode})"); - void SelectDataMode(object dataMode) - { - if (m_ActualView is not IDataModeHandler dataModeHandler) - return; // Something very weird has happened... + menu.AddItem(new GUIContent(autoLabel), + clientDataModeController.isAutomatic, + () => clientDataModeController.SwitchToAutomatic()); - if (dataMode is DataMode mode && dataModeHandler.IsDataModeSupported(mode)) - dataModeHandler.SwitchToDataMode(mode); - else - dataModeHandler.SwitchToDefaultDataMode(); - - UpdateDataMode(dataModeHandler.dataMode, true); - } - - void OnViewDataModeChanged(DataMode newDataMode) => UpdateDataMode(newDataMode, true); - - void UpdateDataMode(DataMode newDataMode, bool needsRepaint) - { - m_CachedDataMode = newDataMode; - m_ShouldDrawDataModeSwitch = ShouldDrawDataModesSwitch(); + menu.AddSeparator(""); - if (needsRepaint) + foreach (var mode in clientDataModeController.supportedDataModes) { - m_ActualView.Repaint(); - RepaintImmediately(); + menu.AddItem(Styles.DataModes.dataModeNameLabels[mode], + !clientDataModeController.isAutomatic && clientDataModeController.dataMode == mode, + () => clientDataModeController.SwitchToStickyDataMode(mode)); } } @@ -920,54 +923,12 @@ internal void Reload(object userData) File.Delete(saveWindowPath); } - readonly List m_DataModeSanitizationCache = new List(3); // Number of modes, minus `Disabled` - - static void SanitizeSupportedDataModesList(IReadOnlyList originalList, List sanitizedList) - { - sanitizedList.Clear(); - - foreach (var mode in originalList) - { - if (mode == DataMode.Disabled) - continue; // Never list `DataMode.Disabled` - - if (sanitizedList.Contains(mode)) - continue; // Prevent duplicate entries - - sanitizedList.Add(mode); - } - - // Ensure we are displaying the data modes in a predefined order, regardless of - // the order in which the user defined their list. - sanitizedList.Sort(); - } protected virtual void AddDefaultItemsToMenu(GenericMenu menu, EditorWindow window) { if (menu.GetItemCount() != 0) menu.AddSeparator(""); - if (m_ShouldDrawDataModeSwitch) - { - // This cast is guaranteed to work by m_ShouldDrawDataModeSwitch - var dataModesHandler = (IDataModeHandler) window; - SanitizeSupportedDataModesList(dataModesHandler.supportedDataModes, m_DataModeSanitizationCache); - - // Don't show anything if only one mode is supported - if (m_DataModeSanitizationCache.Count > 1) - { - foreach (var mode in m_DataModeSanitizationCache) - { - menu.AddItem(Styles.DataModes.dataModeNameLabels[mode], - m_CachedDataMode == mode, - SelectDataMode, - mode); - } - - menu.AddSeparator(""); - } - } - if(window is ISupportsOverlays) { var binding = ShortcutManager.instance.GetShortcutBinding("Overlays/Show Overlay Menu"); diff --git a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs index d8fe397fb..95c124c6d 100644 --- a/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs +++ b/Editor/Mono/ImportSettings/DesktopPluginImporterExtension.cs @@ -131,9 +131,9 @@ internal override void OnGUI(PluginImporterInspector inspector) } } - // Windows has 1 build target per CPU architecture - private readonly DesktopSingleCPUProperty m_WindowsX86; - private readonly DesktopSingleCPUProperty m_WindowsX86_X64; + // Windows has 2 build targets. One build target for 32bit that supports 1 CPU architectue. The other for 64 bit that supports multiple 64 bit architectures (x64 and ARM64). + private readonly DesktopSingleCPUProperty m_Windows32; + private readonly DesktopMultiCPUProperty m_Windows64; // Linux has 1 build target and 1 supported CPU architecture private readonly DesktopSingleCPUProperty m_Linux; @@ -145,15 +145,16 @@ internal override void OnGUI(PluginImporterInspector inspector) public DesktopPluginImporterExtension() : base(null) { - m_WindowsX86 = new DesktopSingleCPUProperty(BuildTarget.StandaloneWindows, DesktopPluginCPUArchitecture.x86); - m_WindowsX86_X64 = new DesktopSingleCPUProperty(BuildTarget.StandaloneWindows64, DesktopPluginCPUArchitecture.x86_64); + m_Windows32 = new DesktopSingleCPUProperty(BuildTarget.StandaloneWindows, DesktopPluginCPUArchitecture.x86); + m_Windows64 = new DesktopMultiCPUProperty(BuildTarget.StandaloneWindows64, DesktopPluginCPUArchitecture.x86_64, DesktopPluginCPUArchitecture.ARM64); + m_Linux = new DesktopSingleCPUProperty(BuildTarget.StandaloneLinux64, DesktopPluginCPUArchitecture.x86_64); m_MacOS = new DesktopMultiCPUProperty(BuildTarget.StandaloneOSX, DesktopPluginCPUArchitecture.x86_64, DesktopPluginCPUArchitecture.ARM64); properties = new Property[] { - m_WindowsX86, - m_WindowsX86_X64, + m_Windows32, + m_Windows64, m_Linux, m_MacOS }; @@ -193,8 +194,8 @@ public override void OnPlatformSettingsGUI(PluginImporterInspector inspector) if (IsUsableOnWindows(imp)) { EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent("Windows"), EditorStyles.boldLabel); - m_WindowsX86.OnGUI(inspector); - m_WindowsX86_X64.OnGUI(inspector); + m_Windows32.OnGUI(inspector); + m_Windows64.OnGUI(inspector); EditorGUILayout.Space(); } diff --git a/Editor/Mono/ImportSettings/TextureImporterInspector.cs b/Editor/Mono/ImportSettings/TextureImporterInspector.cs index ade1eb268..bdb5bc3e9 100644 --- a/Editor/Mono/ImportSettings/TextureImporterInspector.cs +++ b/Editor/Mono/ImportSettings/TextureImporterInspector.cs @@ -364,7 +364,7 @@ internal class Styles public readonly GUIContent psdRemoveMatte = EditorGUIUtility.TrTextContent("Remove PSD Matte", "Enable special processing for PSD that has transparency, as color pixels will be tweaked (blended with white color)."); public readonly GUIContent ignorePngGamma = EditorGUIUtility.TrTextContent("Ignore PNG Gamma", "Ignore the Gamma attribute value in PNG files."); - public readonly GUIContent readWriteWarning = EditorGUIUtility.TrTextContent("Textures larger than 8192 can not be Read/Write enabled. Value will be ignored."); + public readonly GUIContent readWriteWarning = EditorGUIUtility.TrTextContent("When you enable Read/Write, Unity stores an additional copy of your textures in CPU-addressable memory. One or more of the textures you have selected use a considerable amount of memory (more than 512MB)."); public readonly GUIContent flipbookColumns = EditorGUIUtility.TrTextContent("Columns", "Source image is divided into this amount of columns."); public readonly GUIContent flipbookRows = EditorGUIUtility.TrTextContent("Rows", "Source image is divided into this amount of rows."); @@ -979,14 +979,11 @@ void POTScaleGUI(TextureInspectorGUIElement guiElements) void ReadableGUI(TextureInspectorGUIElement guiElements) { - bool enabled = CanReadWrite(); - using (new EditorGUI.DisabledScope(!enabled)) + ToggleFromInt(m_IsReadable, s_Styles.readWrite); + + if (m_IsReadable.intValue > 0 && !m_IsReadable.hasMultipleDifferentValues && ShouldShowWarningForReadWrite()) { - ToggleFromInt(m_IsReadable, s_Styles.readWrite); - if (!enabled && m_IsReadable.intValue > 0) - { - EditorGUILayout.HelpBox(s_Styles.readWriteWarning.text, MessageType.Warning, true); - } + EditorGUILayout.HelpBox(s_Styles.readWriteWarning.text, MessageType.Info, true); } } @@ -1443,6 +1440,7 @@ public override void OnInspectorGUI() // NB we do these weird things partly because ApplyTextureType has early out // NB hence we want settings to have *old* textureType when calling it TextureImporterSettings settings = GetSerializedPropertySettings(); + settings.textureType = (TextureImporterType)oldTextureType; settings.ApplyTextureType((TextureImporterType)newTextureType); settings.textureType = (TextureImporterType)newTextureType; @@ -1678,14 +1676,24 @@ private static bool IsPowerOfTwo(int f) return ((f & (f - 1)) == 0); } - bool CanReadWrite() + bool ShouldShowWarningForReadWrite() { - foreach (TextureImportPlatformSettings ps in m_PlatformSettings) + const int maxRecommendedSize = 536870912; // 512 MB + + if (textureInspector && textureInspector.targets != null) { - if (ps.model.platformTextureSettings.maxTextureSize > TextureImporter.MaxTextureSizeAllowedForReadable) - return false; + foreach (Texture texture in textureInspector.targets) + { + if (texture) + { + if (TextureUtil.GetStorageMemorySizeLong(texture) > maxRecommendedSize) + { + return true; + } + } + } } - return true; + return false; } public virtual void BuildTargetList() diff --git a/Editor/Mono/Inspector/AudioSourceInspector.cs b/Editor/Mono/Inspector/AudioSourceInspector.cs index 27514408e..1679eebe9 100644 --- a/Editor/Mono/Inspector/AudioSourceInspector.cs +++ b/Editor/Mono/Inspector/AudioSourceInspector.cs @@ -494,7 +494,7 @@ private void Audio3DGUI() if (targets.Length == 1) { AudioSource t = (AudioSource)target; - AudioListener audioListener = (AudioListener)FindObjectOfType(typeof(AudioListener)); + AudioListener audioListener = (AudioListener)FindFirstObjectByType(typeof(AudioListener)); if (audioListener != null) { float distToListener = (AudioUtil.GetListenerPos() - t.transform.position).magnitude; diff --git a/Editor/Mono/Inspector/CameraEditorUtils.cs b/Editor/Mono/Inspector/CameraEditorUtils.cs index d1430a24b..bf8b8b776 100644 --- a/Editor/Mono/Inspector/CameraEditorUtils.cs +++ b/Editor/Mono/Inspector/CameraEditorUtils.cs @@ -183,7 +183,7 @@ public static bool TryGetSensorGateFrustum(Camera camera, Vector3[] near, Vector if (near != null) { Vector2 planeSize; - planeSize.y = 2.0f * camera.nearClipPlane * Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5f); + planeSize.y = camera.nearClipPlane * Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5f); planeSize.x = planeSize.y * camera.sensorSize.x / camera.sensorSize.y; Vector3 rightOffset = camera.gameObject.transform.right * planeSize.x; diff --git a/Editor/Mono/Inspector/CameraOverlay.cs b/Editor/Mono/Inspector/CameraOverlay.cs index 6b546205f..45e42b88d 100644 --- a/Editor/Mono/Inspector/CameraOverlay.cs +++ b/Editor/Mono/Inspector/CameraOverlay.cs @@ -10,7 +10,7 @@ namespace UnityEditor { - [Overlay(id = k_OverlayID, displayName = k_DisplayName)] + [Overlay(id = k_OverlayID, displayName = k_DisplayName, defaultDisplay = true)] [Icon("Icons/Overlays/CameraPreview.png")] class SceneViewCameraOverlay : IMGUIOverlay { diff --git a/Editor/Mono/Inspector/Editor.cs b/Editor/Mono/Inspector/Editor.cs index 6c02192e2..c71e2a609 100644 --- a/Editor/Mono/Inspector/Editor.cs +++ b/Editor/Mono/Inspector/Editor.cs @@ -390,6 +390,11 @@ internal InspectorMode inspectorMode } } + internal DataMode dataMode => + propertyViewer is EditorWindow editorWindow + ? editorWindow.dataModeController.dataMode + : DataMode.Disabled; + internal static float kLineHeight = EditorGUI.kSingleLineHeight; internal bool hideInspector = false; @@ -659,6 +664,9 @@ private void CreateSerializedObject() { m_SerializedObject = new SerializedObject(targets, m_Context); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; + AssignCachedProperties(this, m_SerializedObject.GetIterator()); m_EnabledProperty = m_SerializedObject.FindProperty("m_Enabled"); } @@ -1325,10 +1333,12 @@ internal bool CanBeExpandedViaAFoldout() return true; if (m_SerializedObject == null) - CreateSerializedObject(); + m_SerializedObject = new SerializedObject(targets, m_Context); else m_SerializedObject.Update(); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; return CanBeExpandedViaAFoldoutWithoutUpdate(); } @@ -1339,7 +1349,7 @@ internal bool CanBeExpandedViaAFoldoutWithoutUpdate() return true; if (m_SerializedObject == null) - CreateSerializedObject(); + m_SerializedObject = new SerializedObject(targets, m_Context); SerializedProperty property = m_SerializedObject.GetIterator(); bool analyzePropertyChildren = true; diff --git a/Editor/Mono/Inspector/EditorSettingsInspector.cs b/Editor/Mono/Inspector/EditorSettingsInspector.cs index 70cb76e10..ef3323acb 100644 --- a/Editor/Mono/Inspector/EditorSettingsInspector.cs +++ b/Editor/Mono/Inspector/EditorSettingsInspector.cs @@ -93,7 +93,7 @@ class Content public static readonly GUIContent enterPlayModeOptionsEnabled = EditorGUIUtility.TrTextContent("Enter Play Mode Options", "Enables options when Entering Play Mode"); public static readonly GUIContent enterPlayModeOptionsEnableDomainReload = EditorGUIUtility.TrTextContent("Reload Domain", "Enables Domain Reload when Entering Play Mode. Domain reload reinitializes game completely making loading behavior very close to the Player"); public static readonly GUIContent enterPlayModeOptionsEnableSceneReload = EditorGUIUtility.TrTextContent("Reload Scene", "Enables Scene Reload when Entering Play Mode. Scene reload makes loading behavior and performance characteristics very close to the Player"); - public static readonly GUIContent enterPlayModeOptionsDisableSceneBackup = EditorGUIUtility.TrTextContent("Disable Scene Backup", "Conditionally skips writing a backup of the open scenes to disk. Only scenes that are modified in-memory need to be backed up, but making modifications from script may change the scene without setting the scene's dirty flag."); + public static readonly GUIContent enterPlayModeOptionsEnableSceneBackup = EditorGUIUtility.TrTextContent("Scene Backup", "Force writing a backup of all the open scenes to disk even if scenes are not dirty when entering Play mode. Only scenes that are modified in-memory actually need to be backed up, but making modifications from script may change the scene without setting the scene's dirty flag."); public static readonly GUIContent numberingScheme = EditorGUIUtility.TrTextContent("Numbering Scheme"); @@ -991,7 +991,7 @@ private void DoEnterPlayModeSettings() EnterPlayModeOptions options = (EnterPlayModeOptions)m_EnterPlayModeOptions.intValue; options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableDomainReload, Content.enterPlayModeOptionsEnableDomainReload); options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableSceneReload, Content.enterPlayModeOptionsEnableSceneReload); - options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableSceneBackupUnlessDirty, Content.enterPlayModeOptionsDisableSceneBackup); + options = ToggleEnterPlayModeOptions(options, EnterPlayModeOptions.DisableSceneBackupUnlessDirty, Content.enterPlayModeOptionsEnableSceneBackup); if (m_EnterPlayModeOptions.intValue != (int)options) { diff --git a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs index 731ff3045..9ba087e3f 100644 --- a/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs +++ b/Editor/Mono/Inspector/Enlighten/LightmapParameters.cs @@ -72,10 +72,6 @@ public override void OnInspectorGUI() { ++EditorGUI.indentLevel; - EditorGUILayout.LabelField(Styles.enlightenLabel, EditorStyles.boldLabel); - - ++EditorGUI.indentLevel; - EditorGUILayout.PropertyField(m_Resolution, Styles.resolutionContent); EditorGUILayout.Slider(m_ClusterResolution, 0.1F, 1.0F, Styles.clusterResolutionContent); EditorGUILayout.IntSlider(m_IrradianceBudget, 32, 2048, Styles.irradianceBudgetContent); @@ -87,27 +83,35 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); --EditorGUI.indentLevel; - --EditorGUI.indentLevel; } } - GUILayout.Label(Styles.bakedGIContent, EditorStyles.boldLabel); + m_BakedGISettings.value = EditorGUILayout.FoldoutTitlebar(m_BakedGISettings.value, Styles.bakedGIContent, true); - EditorGUILayout.PropertyField(m_AntiAliasingSamples, Styles.antiAliasingSamplesContent); - const float minPushOff = 0.0001f; // Keep in sync with PLM_MIN_PUSHOFF - EditorGUILayout.Slider(m_Pushoff, minPushOff, 1.0f, Styles.pushoffContent); - EditorGUILayout.PropertyField(m_BakedLightmapTag, Styles.bakedLightmapTagContent); - m_LimitLightmapCount.boolValue = EditorGUILayout.Toggle(Styles.limitLightmapCount, m_LimitLightmapCount.boolValue); - if (m_LimitLightmapCount.boolValue) + if (m_BakedGISettings.value) { EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(m_LightmapMaxCount, Styles.lightmapMaxCount); + EditorGUILayout.PropertyField(m_AntiAliasingSamples, Styles.antiAliasingSamplesContent); + const float minPushOff = 0.0001f; // Keep in sync with PLM_MIN_PUSHOFF + EditorGUILayout.Slider(m_Pushoff, minPushOff, 1.0f, Styles.pushoffContent); + EditorGUILayout.PropertyField(m_BakedLightmapTag, Styles.bakedLightmapTagContent); + m_LimitLightmapCount.boolValue = EditorGUILayout.Toggle(Styles.limitLightmapCount, m_LimitLightmapCount.boolValue); + if (m_LimitLightmapCount.boolValue) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_LightmapMaxCount, Styles.lightmapMaxCount); + EditorGUI.indentLevel--; + } EditorGUI.indentLevel--; } - EditorGUILayout.Space(); - - GUILayout.Label(Styles.generalGIContent, EditorStyles.boldLabel); - EditorGUILayout.Slider(m_BackFaceTolerance, 0.0f, 1.0f, Styles.backFaceToleranceContent); + + m_GeneralParametersSettings.value = EditorGUILayout.FoldoutTitlebar(m_GeneralParametersSettings.value, Styles.generalGIContent, true); + if (m_GeneralParametersSettings.value) + { + ++EditorGUI.indentLevel; + EditorGUILayout.Slider(m_BackFaceTolerance, 0.0f, 1.0f, Styles.backFaceToleranceContent); + --EditorGUI.indentLevel; + } serializedObject.ApplyModifiedProperties(); } @@ -126,7 +130,7 @@ private class Styles public static readonly GUIContent clusterResolutionContent = EditorGUIUtility.TrTextContent("Cluster Resolution", "The ratio between the resolution of the clusters with which light bounce is calculated and the resolution of the output lightmaps that sample from these."); public static readonly GUIContent irradianceBudgetContent = EditorGUIUtility.TrTextContent("Irradiance Budget", "The amount of data used by each texel in the output lightmap. Specifies how fine-grained a view of the scene an output texel has. Small values mean more averaged out lighting, since the light contributions from more clusters are treated as one. Affects runtime memory usage and to a lesser degree runtime CPU usage."); public static readonly GUIContent irradianceQualityContent = EditorGUIUtility.TrTextContent("Irradiance Quality", "The number of rays to cast to compute which clusters affect a given output lightmap texel - the granularity of how this is saved is defined by the Irradiance Budget. Affects the speed of the precomputation but has no influence on runtime performance."); - public static readonly GUIContent backFaceToleranceContent = EditorGUIUtility.TrTextContent("Backface Tolerance", "The percentage of rays shot from an output texel that must hit front faces to be considered usable. Allows a texel to be invalidated if too many of the rays cast from it hit back faces (the texel is inside some geometry). In that case artefacts are avoided by cloning valid values from surrounding texels. For example, if backface tolerance is 0.0, the texel is rejected only if it sees nothing but backfaces. If it is 1.0, the ray origin is rejected if it has even one ray that hits a backface."); + public static readonly GUIContent backFaceToleranceContent = EditorGUIUtility.TrTextContent("Backface Tolerance", "Defines the percentage of rays which must hit front-facing geometry for the lightmapper to consider a texel valid. Increasing this number increases the likelihood that texels will be invalidated when backfaces can be seen. Invalid texels will then receive dilation."); public static readonly GUIContent modellingToleranceContent = EditorGUIUtility.TrTextContent("Modelling Tolerance", "Maximum size of gaps that can be ignored for GI."); public static readonly GUIContent edgeStitchingContent = EditorGUIUtility.TrTextContent("Edge Stitching", "If enabled, ensures that UV charts (aka UV islands) in the generated lightmaps blend together where they meet so there is no visible seam between them."); public static readonly GUIContent systemTagContent = EditorGUIUtility.TrTextContent("System Tag", "Systems are groups of objects whose lightmaps are in the same atlas. It is also the granularity at which dependencies are calculated. Multiple systems are created automatically if the scene is big enough, but it can be helpful to be able to split them up manually for e.g. streaming in sections of a level. The system tag lets you force an object into a different realtime system even though all the other parameters are the same."); diff --git a/Editor/Mono/Inspector/GenericInspector.cs b/Editor/Mono/Inspector/GenericInspector.cs index 1f3dac789..777c31bea 100644 --- a/Editor/Mono/Inspector/GenericInspector.cs +++ b/Editor/Mono/Inspector/GenericInspector.cs @@ -68,11 +68,19 @@ internal override bool GetOptimizedGUIBlock(bool isDirty, bool isVisible, out fl // Update serialized object representation if (m_SerializedObject == null) - m_SerializedObject = new SerializedObject(targets, m_Context) { inspectorMode = inspectorMode }; + { + m_SerializedObject = new SerializedObject(targets, m_Context) + { + inspectorMode = inspectorMode, + inspectorDataMode = dataMode + }; + } else { m_SerializedObject.Update(); m_SerializedObject.inspectorMode = inspectorMode; + if (m_SerializedObject.inspectorDataMode != dataMode) + m_SerializedObject.inspectorDataMode = dataMode; } height = 0; diff --git a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs index a00d445e8..947f1dcbf 100644 --- a/Editor/Mono/Inspector/GraphicsSettingsInspector.cs +++ b/Editor/Mono/Inspector/GraphicsSettingsInspector.cs @@ -43,6 +43,10 @@ internal class Styles public static readonly GUIContent cameraSettings = EditorGUIUtility.TrTextContent("Camera Settings"); public static readonly GUIContent renderPipeSettings = EditorGUIUtility.TrTextContent("Scriptable Render Pipeline Settings", "This defines the default render pipeline, which Unity uses when there is no override for a given quality level."); public static readonly GUIContent renderPipeLabel = EditorGUIUtility.TrTextContent("Scriptable Render Pipeline"); + public static readonly GUIContent cullingSettings = EditorGUIUtility.TrTextContent("Culling Settings"); + public static readonly GUIContent cameraRelativeSettings = EditorGUIUtility.TrTextContent("Camera-Relative Culling"); + public static readonly GUIContent cameraRelativeLightCulling = EditorGUIUtility.TrTextContent("Lights", "When enabled, Unity uses the camera position as the reference point for culling lights instead of the world space origin."); + public static readonly GUIContent cameraRelativeShadowCulling = EditorGUIUtility.TrTextContent("Shadows", "When enabled, Unity uses the camera position as the reference point for culling shadows instead of the world space origin."); } Editor m_TierSettingsEditor; @@ -56,6 +60,8 @@ internal class Styles SerializedProperty m_ScriptableRenderLoop; SerializedProperty m_LogWhenShaderIsCompiled; SerializedProperty m_LightProbeOutsideHullStrategy; + SerializedProperty m_CameraRelativeLightCulling; + SerializedProperty m_CameraRelativeShadowCulling; Object graphicsSettings { @@ -100,6 +106,8 @@ public void OnEnable() m_LogWhenShaderIsCompiled = serializedObject.FindProperty("m_LogWhenShaderIsCompiled"); m_LightProbeOutsideHullStrategy = serializedObject.FindProperty("m_LightProbeOutsideHullStrategy"); tierSettingsAnimator = new AnimatedValues.AnimBool(showTierSettingsUI, Repaint); + m_CameraRelativeLightCulling = serializedObject.FindProperty("m_CameraRelativeLightCulling"); + m_CameraRelativeShadowCulling = serializedObject.FindProperty("m_CameraRelativeShadowCulling"); } private void HandleEditorWindowButton() @@ -200,6 +208,16 @@ public override void OnInspectorGUI() shaderPreloadEditor.OnInspectorGUI(); + EditorGUILayout.Space(); + GUILayout.Label(Styles.cullingSettings, EditorStyles.boldLabel); + EditorGUI.indentLevel++; + EditorGUILayout.LabelField(Styles.cameraRelativeSettings, EditorStyles.label); + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_CameraRelativeLightCulling, Styles.cameraRelativeLightCulling); + EditorGUILayout.PropertyField(m_CameraRelativeShadowCulling, Styles.cameraRelativeShadowCulling); + EditorGUI.indentLevel--; + EditorGUI.indentLevel--; + serializedObject.ApplyModifiedProperties(); } diff --git a/Editor/Mono/Inspector/InspectorWindow.cs b/Editor/Mono/Inspector/InspectorWindow.cs index 79ac72112..52ff6d11d 100644 --- a/Editor/Mono/Inspector/InspectorWindow.cs +++ b/Editor/Mono/Inspector/InspectorWindow.cs @@ -96,8 +96,6 @@ protected override void OnEnable() EditorApplication.projectWasLoaded += OnProjectWasLoaded; Selection.selectionChanged += OnSelectionChanged; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; - - UpdateDataMode(); } private void OnAfterAssemblyReload() @@ -158,32 +156,22 @@ private void OnSelectionChanged() m_MultiEditLabel.RemoveFromHierarchy(); } - UpdateDataMode(); - } - - private void UpdateDataMode() - { - UpdateSupportedDataModes(); + if (isLocked) + return; - // Try to respect the DataMode hint provided by the selection. - // If impossible, try to retain the current DataMode if supported. - if (IsDataModeSupported(Selection.dataModeHint)) - SwitchToDataMode(Selection.dataModeHint); - else if (!IsDataModeSupported(dataMode)) - SwitchToDefaultDataMode(); + UpdateSupportedDataModesList(); } // Note: supportedModes is cleared before and sorted after this method is called protected override void OnUpdateSupportedDataModes(List supportedModes) { - m_UserSupportedDataModes.Clear(); - // Not showing data modes in debug - if (m_InspectorMode == InspectorMode.Normal) - { - DataModeSupportUtils.GetDataModeSupport(Selection.activeObject, Selection.activeContext, m_UserSupportedDataModes); - supportedModes.AddRange(m_UserSupportedDataModes); - } + if (m_InspectorMode != InspectorMode.Normal) + return; + + m_UserSupportedDataModes.Clear(); + DataModeSupportUtils.GetDataModeSupport(Selection.activeObject, Selection.activeContext, m_UserSupportedDataModes); + supportedModes.AddRange(m_UserSupportedDataModes); } protected override void OnDisable() diff --git a/Editor/Mono/Inspector/LightProbeGroupInspector.cs b/Editor/Mono/Inspector/LightProbeGroupInspector.cs index 6cf61c3cd..995bfd533 100644 --- a/Editor/Mono/Inspector/LightProbeGroupInspector.cs +++ b/Editor/Mono/Inspector/LightProbeGroupInspector.cs @@ -255,7 +255,7 @@ public void HandleEditMenuHotKeyCommands() public static void TetrahedralizeSceneProbes(out Vector3[] positions, out int[] indices) { - var probeGroups = Object.FindObjectsOfType(typeof(LightProbeGroup)) as LightProbeGroup[]; + var probeGroups = Object.FindObjectsByType(FindObjectsSortMode.None); if (probeGroups == null) { diff --git a/Editor/Mono/Inspector/LightingSettingsEditor.cs b/Editor/Mono/Inspector/LightingSettingsEditor.cs index fcb0f2a06..9fa72a4b9 100644 --- a/Editor/Mono/Inspector/LightingSettingsEditor.cs +++ b/Editor/Mono/Inspector/LightingSettingsEditor.cs @@ -280,67 +280,67 @@ public void OnGUI(bool compact, bool drawAutoGenerate) InternalSettingsGUI(compact); } - public void UpdateSettings(SerializedObject lso) + public void UpdateSettings(SerializedObject lightingSettingsObject) { - if (lso != null) - { - m_GIWorkflowMode = lso.FindProperty("m_GIWorkflowMode"); - - //realtime GI - m_RealtimeResolution = lso.FindProperty("m_RealtimeResolution"); - m_EnableRealtimeGI = lso.FindProperty("m_EnableRealtimeLightmaps"); - m_RealtimeEnvironmentLighting = lso.FindProperty("m_RealtimeEnvironmentLighting"); - - //baked - m_EnabledBakedGI = lso.FindProperty("m_EnableBakedLightmaps"); - m_BakeBackend = lso.FindProperty("m_BakeBackend"); - m_MixedBakeMode = lso.FindProperty("m_MixedBakeMode"); - m_AlbedoBoost = lso.FindProperty("m_AlbedoBoost"); - m_IndirectOutputScale = lso.FindProperty("m_IndirectOutputScale"); - m_LightmapMaxSize = lso.FindProperty("m_LightmapMaxSize"); - m_LightmapParameters = lso.FindProperty("m_LightmapParameters"); - m_LightmapDirectionalMode = lso.FindProperty("m_LightmapsBakeMode"); - m_BakeResolution = lso.FindProperty("m_BakeResolution"); - m_Padding = lso.FindProperty("m_Padding"); - m_AmbientOcclusion = lso.FindProperty("m_AO"); - m_AOMaxDistance = lso.FindProperty("m_AOMaxDistance"); - m_CompAOExponent = lso.FindProperty("m_CompAOExponent"); - m_CompAOExponentDirect = lso.FindProperty("m_CompAOExponentDirect"); - m_LightmapCompression = lso.FindProperty("m_LightmapCompression"); - - // pvr - m_PVRSampleCount = lso.FindProperty("m_PVRSampleCount"); - m_PVRDirectSampleCount = lso.FindProperty("m_PVRDirectSampleCount"); - m_PVRBounces = lso.FindProperty("m_PVRBounces"); - m_PVRCulling = lso.FindProperty("m_PVRCulling"); - m_PVRFilteringMode = lso.FindProperty("m_PVRFilteringMode"); - m_PVRFilterTypeDirect = lso.FindProperty("m_PVRFilterTypeDirect"); - m_PVRFilterTypeIndirect = lso.FindProperty("m_PVRFilterTypeIndirect"); - m_PVRFilterTypeAO = lso.FindProperty("m_PVRFilterTypeAO"); - m_PVRDenoiserTypeDirect = lso.FindProperty("m_PVRDenoiserTypeDirect"); - m_PVRDenoiserTypeIndirect = lso.FindProperty("m_PVRDenoiserTypeIndirect"); - m_PVRDenoiserTypeAO = lso.FindProperty("m_PVRDenoiserTypeAO"); - m_PVRFilteringGaussRadiusDirect = lso.FindProperty("m_PVRFilteringGaussRadiusDirect"); - m_PVRFilteringGaussRadiusIndirect = lso.FindProperty("m_PVRFilteringGaussRadiusIndirect"); - m_PVRFilteringGaussRadiusAO = lso.FindProperty("m_PVRFilteringGaussRadiusAO"); - m_PVRFilteringAtrousPositionSigmaDirect = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaDirect"); - m_PVRFilteringAtrousPositionSigmaIndirect = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaIndirect"); - m_PVRFilteringAtrousPositionSigmaAO = lso.FindProperty("m_PVRFilteringAtrousPositionSigmaAO"); - m_PVREnvironmentIS = lso.FindProperty("m_PVREnvironmentImportanceSampling"); - m_PVREnvironmentSampleCount = lso.FindProperty("m_PVREnvironmentSampleCount"); - m_LightProbeSampleCountMultiplier = lso.FindProperty("m_LightProbeSampleCountMultiplier"); - - //dev debug properties - m_ExportTrainingData = lso.FindProperty("m_ExportTrainingData"); - m_TrainingDataDestination = lso.FindProperty("m_TrainingDataDestination"); - m_ForceWhiteAlbedo = lso.FindProperty("m_ForceWhiteAlbedo"); - m_ForceUpdates = lso.FindProperty("m_ForceUpdates"); - m_FilterMode = lso.FindProperty("m_FilterMode"); - m_BounceScale = lso.FindProperty("m_BounceScale"); - m_TiledBaking = lso.FindProperty("m_PVRTiledBaking"); - m_NumRaysToShootPerTexel = lso.FindProperty("m_NumRaysToShootPerTexel"); - m_RespectSceneVisibilityWhenBakingGI = lso.FindProperty("m_RespectSceneVisibilityWhenBakingGI"); - } + if (lightingSettingsObject == null) + return; + + m_GIWorkflowMode = lightingSettingsObject.FindProperty("m_GIWorkflowMode"); + + //realtime GI + m_RealtimeResolution = lightingSettingsObject.FindProperty("m_RealtimeResolution"); + m_EnableRealtimeGI = lightingSettingsObject.FindProperty("m_EnableRealtimeLightmaps"); + m_RealtimeEnvironmentLighting = lightingSettingsObject.FindProperty("m_RealtimeEnvironmentLighting"); + + //baked + m_EnabledBakedGI = lightingSettingsObject.FindProperty("m_EnableBakedLightmaps"); + m_BakeBackend = lightingSettingsObject.FindProperty("m_BakeBackend"); + m_MixedBakeMode = lightingSettingsObject.FindProperty("m_MixedBakeMode"); + m_AlbedoBoost = lightingSettingsObject.FindProperty("m_AlbedoBoost"); + m_IndirectOutputScale = lightingSettingsObject.FindProperty("m_IndirectOutputScale"); + m_LightmapMaxSize = lightingSettingsObject.FindProperty("m_LightmapMaxSize"); + m_LightmapParameters = lightingSettingsObject.FindProperty("m_LightmapParameters"); + m_LightmapDirectionalMode = lightingSettingsObject.FindProperty("m_LightmapsBakeMode"); + m_BakeResolution = lightingSettingsObject.FindProperty("m_BakeResolution"); + m_Padding = lightingSettingsObject.FindProperty("m_Padding"); + m_AmbientOcclusion = lightingSettingsObject.FindProperty("m_AO"); + m_AOMaxDistance = lightingSettingsObject.FindProperty("m_AOMaxDistance"); + m_CompAOExponent = lightingSettingsObject.FindProperty("m_CompAOExponent"); + m_CompAOExponentDirect = lightingSettingsObject.FindProperty("m_CompAOExponentDirect"); + m_LightmapCompression = lightingSettingsObject.FindProperty("m_LightmapCompression"); + + // pvr + m_PVRSampleCount = lightingSettingsObject.FindProperty("m_PVRSampleCount"); + m_PVRDirectSampleCount = lightingSettingsObject.FindProperty("m_PVRDirectSampleCount"); + m_PVRBounces = lightingSettingsObject.FindProperty("m_PVRBounces"); + m_PVRCulling = lightingSettingsObject.FindProperty("m_PVRCulling"); + m_PVRFilteringMode = lightingSettingsObject.FindProperty("m_PVRFilteringMode"); + m_PVRFilterTypeDirect = lightingSettingsObject.FindProperty("m_PVRFilterTypeDirect"); + m_PVRFilterTypeIndirect = lightingSettingsObject.FindProperty("m_PVRFilterTypeIndirect"); + m_PVRFilterTypeAO = lightingSettingsObject.FindProperty("m_PVRFilterTypeAO"); + m_PVRDenoiserTypeDirect = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeDirect"); + m_PVRDenoiserTypeIndirect = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeIndirect"); + m_PVRDenoiserTypeAO = lightingSettingsObject.FindProperty("m_PVRDenoiserTypeAO"); + m_PVRFilteringGaussRadiusDirect = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusDirect"); + m_PVRFilteringGaussRadiusIndirect = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusIndirect"); + m_PVRFilteringGaussRadiusAO = lightingSettingsObject.FindProperty("m_PVRFilteringGaussRadiusAO"); + m_PVRFilteringAtrousPositionSigmaDirect = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaDirect"); + m_PVRFilteringAtrousPositionSigmaIndirect = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaIndirect"); + m_PVRFilteringAtrousPositionSigmaAO = lightingSettingsObject.FindProperty("m_PVRFilteringAtrousPositionSigmaAO"); + m_PVREnvironmentIS = lightingSettingsObject.FindProperty("m_PVREnvironmentImportanceSampling"); + m_PVREnvironmentSampleCount = lightingSettingsObject.FindProperty("m_PVREnvironmentSampleCount"); + m_LightProbeSampleCountMultiplier = lightingSettingsObject.FindProperty("m_LightProbeSampleCountMultiplier"); + + //dev debug properties + m_ExportTrainingData = lightingSettingsObject.FindProperty("m_ExportTrainingData"); + m_TrainingDataDestination = lightingSettingsObject.FindProperty("m_TrainingDataDestination"); + m_ForceWhiteAlbedo = lightingSettingsObject.FindProperty("m_ForceWhiteAlbedo"); + m_ForceUpdates = lightingSettingsObject.FindProperty("m_ForceUpdates"); + m_FilterMode = lightingSettingsObject.FindProperty("m_FilterMode"); + m_BounceScale = lightingSettingsObject.FindProperty("m_BounceScale"); + m_TiledBaking = lightingSettingsObject.FindProperty("m_PVRTiledBaking"); + m_NumRaysToShootPerTexel = lightingSettingsObject.FindProperty("m_NumRaysToShootPerTexel"); + m_RespectSceneVisibilityWhenBakingGI = lightingSettingsObject.FindProperty("m_RespectSceneVisibilityWhenBakingGI"); } // Private methods @@ -507,19 +507,16 @@ void GeneralLightmapSettingsGUI(bool compact) { BakeBackendGUI(); - if (lightmapperSupported && !m_BakeBackend.hasMultipleDifferentValues) + if (lightmapperSupported) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(m_PVRCulling, Styles.culling); EditorGUILayout.PropertyField(m_PVREnvironmentIS, Styles.environmentImportanceSampling); - int sampleCount = EditorGUILayout.DelayedIntField(Styles.directSampleCount, m_PVRDirectSampleCount.intValue); - m_PVRDirectSampleCount.intValue = sampleCount; - sampleCount = EditorGUILayout.DelayedIntField(Styles.indirectSampleCount, m_PVRSampleCount.intValue); - m_PVRSampleCount.intValue = sampleCount; - sampleCount = EditorGUILayout.DelayedIntField(Styles.environmentSampleCount, m_PVREnvironmentSampleCount.intValue); - m_PVREnvironmentSampleCount.intValue = sampleCount; + MultiEditableDelayedIntField(m_PVRDirectSampleCount, Styles.directSampleCount); + MultiEditableDelayedIntField(m_PVRSampleCount, Styles.indirectSampleCount); + MultiEditableDelayedIntField(m_PVREnvironmentSampleCount, Styles.environmentSampleCount); using (new EditorGUI.DisabledScope(EditorSettings.useLegacyProbeSampleCount)) { @@ -689,10 +686,13 @@ void InternalSettingsGUI(bool compact) if (m_ShowInternalSettings.value) { + bool enableRealtimeGI = (m_EnableRealtimeGI.boolValue && !m_EnableRealtimeGI.hasMultipleDifferentValues); EditorGUI.indentLevel++; - - EditorGUILayout.PropertyField(m_ForceWhiteAlbedo, Styles.forceWhiteAlbedo); - EditorGUILayout.PropertyField(m_ForceUpdates, Styles.forceUpdates); + if (enableRealtimeGI) + { + EditorGUILayout.PropertyField(m_ForceWhiteAlbedo, Styles.forceWhiteAlbedo); + EditorGUILayout.PropertyField(m_ForceUpdates, Styles.forceUpdates); + } EditorGUILayout.PropertyField(m_ExportTrainingData, Styles.exportTrainingData); @@ -704,10 +704,10 @@ void InternalSettingsGUI(bool compact) } EditorGUILayout.PropertyField(m_FilterMode, Styles.filterMode); - EditorGUILayout.Slider(m_BounceScale, 0.0f, 10.0f, Styles.bounceScale); - if (m_BakeBackend.intValue == (int)LightingSettings.Lightmapper.ProgressiveGPU) - EditorGUILayout.IntPopup(m_TiledBaking, Styles.tiledBakingStrings, Styles.tiledBakingValues, Styles.tiledBaking); - + if (enableRealtimeGI) + { + EditorGUILayout.Slider(m_BounceScale, 0.0f, 10.0f, Styles.bounceScale); + } EditorGUI.indentLevel--; EditorGUILayout.Space(); } @@ -730,26 +730,12 @@ static void DrawPropertyFieldWithPostfixLabel(SerializedProperty property, GUICo const float minimumWidth = 170.0f; const float postfixLabelWidth = 80.0f; - switch (property.propertyType) - { - case SerializedPropertyType.Float: - DrawFieldWithPostfixLabel( - (Rect propertyRect) => { property.floatValue = EditorGUI.FloatField(propertyRect, label, property.floatValue); }, - postfixLabel, - EditorStyles.numberField, - minimumWidth, - postfixLabelWidth); - break; - - case SerializedPropertyType.Integer: - DrawFieldWithPostfixLabel( - (Rect propertyRect) => { property.intValue = EditorGUI.IntField(propertyRect, label, property.intValue); }, - postfixLabel, - EditorStyles.numberField, - minimumWidth, - postfixLabelWidth); - break; - } + DrawFieldWithPostfixLabel( + (Rect propertyRect) => { EditorGUI.PropertyField(propertyRect, property, label); }, + postfixLabel, + EditorStyles.numberField, + minimumWidth, + postfixLabelWidth); } static void DrawFilterSettingField(SerializedProperty gaussSetting, @@ -965,5 +951,27 @@ void DrawDenoiserTypeDropdown(SerializedProperty prop, GUIContent label, Denoise } EditorGUI.EndProperty(); } + + void MultiEditableDelayedIntField(SerializedProperty property, GUIContent style) + { + if (property.hasMultipleDifferentValues) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = true; + + int fieldValue = EditorGUILayout.DelayedIntField(style, property.intValue); + + if (EditorGUI.EndChangeCheck()) + property.intValue = fieldValue; + + EditorGUI.showMixedValue = false; + } + + else + { + int fieldValue = EditorGUILayout.DelayedIntField(style, property.intValue); + property.intValue = fieldValue; + } + } } } diff --git a/Editor/Mono/Inspector/MaterialEditor.cs b/Editor/Mono/Inspector/MaterialEditor.cs index 3c5f7b4ea..db33ce074 100644 --- a/Editor/Mono/Inspector/MaterialEditor.cs +++ b/Editor/Mono/Inspector/MaterialEditor.cs @@ -1385,17 +1385,27 @@ internal static Color ColorPropertyInternal(Rect position, MaterialProperty prop } public Vector4 VectorProperty(MaterialProperty prop, string label) + { + return VectorProperty(prop, new GUIContent(label)); + } + + public Vector4 VectorProperty(MaterialProperty prop, GUIContent label) { Rect r = GetPropertyRect(prop, label, true); return VectorProperty(r, prop, label); } - + public Vector4 VectorProperty(Rect position, MaterialProperty prop, string label) + { + return VectorPropertyInternal(position, prop, new GUIContent(label)); + } + + public Vector4 VectorProperty(Rect position, MaterialProperty prop, GUIContent label) { return VectorPropertyInternal(position, prop, label); } - internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProperty prop, in string label) + internal static Vector4 VectorPropertyInternal(in Rect position, in MaterialProperty prop, in GUIContent label) { BeginProperty(position, prop); @@ -1563,6 +1573,11 @@ public Rect GetTexturePropertyCustomArea(Rect position) } public Texture TextureProperty(Rect position, MaterialProperty prop, string label) + { + return TextureProperty(position, prop, new GUIContent(label, string.Empty)); + } + + public Texture TextureProperty(Rect position, MaterialProperty prop, GUIContent label) { bool scaleOffset = ((prop.flags & MaterialProperty.PropFlags.NoScaleOffset) == 0); return TextureProperty(position, prop, label, scaleOffset); @@ -1572,14 +1587,19 @@ public Texture TextureProperty(Rect position, MaterialProperty prop, string labe { return TextureProperty(position, prop, label, string.Empty, scaleOffset); } - + public Texture TextureProperty(Rect position, MaterialProperty prop, string label, string tooltip, bool scaleOffset) + { + return TextureProperty(position, prop, new GUIContent(label, tooltip), scaleOffset); + } + + public Texture TextureProperty(Rect position, MaterialProperty prop, GUIContent label, bool scaleOffset) { Rect scopeRect = new Rect(position.x, position.y, position.width, EditorGUI.lineHeight); BeginProperty(scopeRect, prop); // Label - EditorGUI.PrefixLabel(position, new GUIContent(label, tooltip)); + EditorGUI.PrefixLabel(position, label); // Texture slot position.height = GetTextureFieldHeight(); @@ -2030,10 +2050,10 @@ internal void DefaultShaderPropertyInternal(Rect position, MaterialProperty prop ColorPropertyInternal(position, prop, label); break; case MaterialProperty.PropType.Texture: // textures - TextureProperty(position, prop, label.text); + TextureProperty(position, prop, label); break; case MaterialProperty.PropType.Vector: // vectors - VectorProperty(position, prop, label.text); + VectorProperty(position, prop, label); break; default: GUI.Label(position, "Unknown property type: " + prop.name + ": " + (int)prop.type); @@ -2920,7 +2940,7 @@ internal void HandleSkybox(GameObject go, Event evt) { if (s_OriginalMaterial == null) { - Undo.RecordObject(FindObjectOfType(), Styles.undoAssignSkyboxMaterial); + Undo.RecordObject(FindFirstObjectByType(), Styles.undoAssignSkyboxMaterial); s_OriginalMaterial = RenderSettings.skybox; } RenderSettings.skybox = target as Material; diff --git a/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs b/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs index be443ca48..f28402c69 100644 --- a/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs +++ b/Editor/Mono/Inspector/MinMaxCurvePropertyDrawer.cs @@ -262,7 +262,7 @@ PropertyField PrepareProperty(SerializedProperty prop, string label, VisualEleme var region = new IMGUIContainer(() => { - var label = new GUIContent(property.name); + var label = new GUIContent(preferredLabel); var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); EditorGUI.LabelField(rect, label); rect = EditorGUI.PrefixLabel(rect, label); @@ -296,7 +296,7 @@ PropertyField PrepareProperty(SerializedProperty prop, string label, VisualEleme if (state == MinMaxCurveState.k_Scalar) { label.ResetPositionProperties(); - label.text = property.name; + label.text = preferredLabel; } else { diff --git a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs index 91f46dade..93152a3c1 100644 --- a/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs +++ b/Editor/Mono/Inspector/MinMaxGradientPropertyDrawer.cs @@ -135,7 +135,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { Init(property); - return new MinMaxGradientField(m_Property, property.localizedDisplayName); + return new MinMaxGradientField(m_Property, preferredLabel); } } } diff --git a/Editor/Mono/Inspector/NotSupportedOnRenderPipelineInspector.cs b/Editor/Mono/Inspector/NotSupportedOnRenderPipelineInspector.cs index 27bce1200..6426e2f3c 100644 --- a/Editor/Mono/Inspector/NotSupportedOnRenderPipelineInspector.cs +++ b/Editor/Mono/Inspector/NotSupportedOnRenderPipelineInspector.cs @@ -12,5 +12,10 @@ public override VisualElement CreateInspectorGUI() { return new HelpBox("This component is not supported on the currently active render pipeline.", HelpBoxMessageType.Warning); } + + public override void OnInspectorGUI() + { + EditorGUILayout.HelpBox("This component is not supported on the currently active render pipeline.", MessageType.Warning); + } } } diff --git a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs index fe04dd224..923a677d1 100644 --- a/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs +++ b/Editor/Mono/Inspector/PlayerSettingsEditor/PlayerSettingsEditor.cs @@ -1714,6 +1714,19 @@ public static T BuildEnumPopup(GUIContent uiString, T selected, T[] options, return options[newIdx]; } + public static void EnumPropertyField(SerializedProperty property, GUIContent name) where T : Enum + { + using (var horizontal = new EditorGUILayout.HorizontalScope()) + { + using (new EditorGUI.PropertyScope(horizontal.rect, GUIContent.none, property)) + { + var values = (T[])Enum.GetValues(typeof(T)); + var valueNames = Enum.GetNames(typeof(T)).Select(e => new GUIContent(e)).ToArray(); + PlayerSettingsEditor.BuildEnumPopup(property, name, values, valueNames); + } + } + } + public void OtherSectionGUI(BuildPlatform platform, ISettingEditorExtension settingsExtension, int sectionIndex = 4) { if (BeginSettingsBox(sectionIndex, SettingsContent.otherSettingsTitle)) diff --git a/Editor/Mono/Inspector/PropertyEditor.cs b/Editor/Mono/Inspector/PropertyEditor.cs index 9bf3f126b..e6cc5146a 100644 --- a/Editor/Mono/Inspector/PropertyEditor.cs +++ b/Editor/Mono/Inspector/PropertyEditor.cs @@ -47,7 +47,7 @@ interface IPropertySourceOpener Object hoveredObject { get; } } - class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu, IDataModeHandlerAndDispatcher + class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu { internal const string k_AssetPropertiesMenuItemName = "Assets/Properties... _&P"; protected const string s_MultiEditClassName = "unity-inspector-no-multi-edit-warning"; @@ -126,6 +126,9 @@ class PropertyEditor : EditorWindow, IPropertyView, IHasCustomMenu, IDataModeHan protected bool m_HasPreview; protected HashSet m_DrawnSelection = new HashSet(); + List m_SupportedDataModes = new(4); + static readonly List k_DisabledDataModes = new() {DataMode.Disabled}; + public GUIView parent => m_Parent; public HashSet editorsWithImportedObjectLabel { get; } = new HashSet(); public EditorDragging editorDragging { get; } @@ -323,10 +326,10 @@ protected virtual void OnEnable() m_PreviewResizer.Init("InspectorPreview"); m_LabelGUI.OnEnable(); m_FirstInitialize = true; + var shouldUpdateSupportedDataModes = m_SerializedDataModeController == null; CreateTracker(); EditorApplication.focusChanged += OnFocusChanged; - EditorApplication.playModeStateChanged += OnPlayModeStateChanged; Undo.undoRedoEvent += OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked += OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished += OnObjectChanged; @@ -337,6 +340,12 @@ protected virtual void OnEnable() rootVisualElement.RegisterCallback(OnMouseLeave); rootVisualElement.RegisterCallback(OnFocusIn); rootVisualElement.RegisterCallback(OnFocusOut); + + dataModeController.dataModeChanged += OnDataModeChanged; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + + if (shouldUpdateSupportedDataModes) + EditorApplication.CallDelayed(UpdateSupportedDataModesList); } [UsedImplicitly] @@ -349,7 +358,6 @@ protected virtual void OnDisable() m_LastVerticalScrollValue = m_ScrollView?.verticalScroller.value ?? 0; EditorApplication.focusChanged -= OnFocusChanged; - EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; Undo.undoRedoEvent -= OnUndoRedoPerformed; PrefabUtility.prefabInstanceUnpacked -= OnPrefabInstanceUnpacked; ObjectChangeEvents.changesPublished -= OnObjectChanged; @@ -360,6 +368,9 @@ protected virtual void OnDisable() rootVisualElement.UnregisterCallback(OnMouseLeave); rootVisualElement.UnregisterCallback(OnFocusIn); rootVisualElement.UnregisterCallback(OnFocusOut); + + dataModeController.dataModeChanged -= OnDataModeChanged; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; } private void OnMouseEnter(MouseEnterEvent e) => HoveredPropertyEditor = this; @@ -687,7 +698,6 @@ protected virtual void CreateTracker() m_Tracker = new ActiveEditorTracker { inspectorMode = InspectorMode.Normal }; if (LoadPersistedObject()) { - dataMode = GetLastKnownDataMode(); m_Tracker.ForceRebuild(); } } @@ -1075,10 +1085,6 @@ internal virtual void RebuildContentsContainers() ScriptAttributeUtility.ClearGlobalCache(); EndRebuildContentContainers(); - - if (dataMode == DataMode.Disabled) - SwitchToDefaultDataMode(); - Repaint(); RefreshTitle(); } @@ -2372,123 +2378,52 @@ static void OpenHoveredItemPropertyEditor(ShortcutManagement.ShortcutArguments a } } - static readonly List k_DisabledDataModes = new() {DataMode.Disabled}; - - readonly List m_SupportedDataModes = new(4); - - bool areDataModesEnabled => - m_InspectorMode == InspectorMode.Normal && - m_SupportedDataModes.Count > 0; - - List GetSupportedDataModesForContext() => - areDataModesEnabled && m_SupportedDataModes.Count > 0 - ? m_SupportedDataModes - : k_DisabledDataModes; - - DataMode GetLastKnownDataMode() - { - if (areDataModesEnabled) - return EditorApplication.isPlaying - ? m_LastKnownPlayModeDataMode - : m_LastKnownEditModeDataMode; - - return DataMode.Disabled; - } - - [SerializeField] DataMode m_LastKnownEditModeDataMode = DataMode.Authoring; - [SerializeField] DataMode m_LastKnownPlayModeDataMode = DataMode.Runtime; - - public event Action dataModeChanged; - - // Caching the DataMode to avoid round-trips to native multiple times per frame. - // Note: DataMode.Disabled is the same default as in ActiveEditorTracker.cpp - DataMode m_DataMode = DataMode.Disabled; - - public DataMode dataMode - { - get => m_DataMode; - private set - { - if (m_DataMode == value) - return; - - tracker.dataMode = value; - m_DataMode = value; - } - } + internal static DataMode GetPreferredDataMode() + => EditorApplication.isPlaying + ? DataMode.Mixed + : DataMode.Authoring; - public IReadOnlyList supportedDataModes => GetSupportedDataModesForContext(); + List GetSupportedDataModes() => m_SupportedDataModes; - public bool IsDataModeSupported(DataMode mode) => GetSupportedDataModesForContext().Contains(mode); + bool m_IsEnteringPlaymode; - public void SwitchToNextDataMode() + void OnPlayModeStateChanged(PlayModeStateChange stateChange) { - var possibleDataModes = GetSupportedDataModesForContext(); - var nextIndex = possibleDataModes.IndexOf(dataMode) + 1; - if (nextIndex >= possibleDataModes.Count) - nextIndex = 0; + if (stateChange is not (PlayModeStateChange.EnteredEditMode or PlayModeStateChange.EnteredPlayMode)) + return; - SwitchToDataMode(possibleDataModes[nextIndex]); + m_IsEnteringPlaymode = true; + UpdateSupportedDataModesList(); } - public void SwitchToDefaultDataMode() + void OnDataModeChanged(DataModeChangeEventArgs evt) { - var selectedDataMode = GetLastKnownDataMode(); - - if (!IsDataModeSupported(selectedDataMode)) - { - // Fallback - selectedDataMode = supportedDataModes[0]; - } - - SwitchToDataMode(selectedDataMode); + tracker.dataMode = evt.nextDataMode; + tracker.ForceRebuild(); } - public void SwitchToDataMode(DataMode mode) + protected void UpdateSupportedDataModesList() { - if (!IsDataModeSupported(mode)) - SwitchToDefaultDataMode(); + m_SupportedDataModes.Clear(); + OnUpdateSupportedDataModes(m_SupportedDataModes); + m_SupportedDataModes.Sort(); - if (dataMode == mode) - return; + if (m_InspectorMode != InspectorMode.Normal || m_SupportedDataModes.Count == 0) + m_SupportedDataModes = k_DisabledDataModes; - dataMode = mode; - tracker.ForceRebuild(); + var dataMode = Selection.dataModeHint == DataMode.Disabled ? GetPreferredDataMode() : Selection.dataModeHint; - dataModeChanged?.Invoke(dataMode); - } - - void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange) - { - switch (playModeStateChange) + // When entering playmode, Inspector relies on getting selection hint + // from Hierarchy window to switch to the proper data mode. + // However when inspector is locked, selection is locked too. + // Here we force the preferred data mode to inspector for this special case. + if (m_IsEnteringPlaymode && m_Tracker.isLocked) { - case PlayModeStateChange.ExitingEditMode: - { - // We're about to exit edit mode, save the last known DataMode - m_LastKnownEditModeDataMode = dataMode; - break; - } - case PlayModeStateChange.ExitingPlayMode: - { - // We're about to exit play mode, save the last known DataMode - m_LastKnownPlayModeDataMode = dataMode; - break; - } - case PlayModeStateChange.EnteredEditMode: - case PlayModeStateChange.EnteredPlayMode: - { - UpdateSupportedDataModes(); - SwitchToDefaultDataMode(); - break; - } + dataMode = GetPreferredDataMode(); + m_IsEnteringPlaymode = false; } - } - protected void UpdateSupportedDataModes() - { - m_SupportedDataModes.Clear(); - OnUpdateSupportedDataModes(m_SupportedDataModes); - m_SupportedDataModes.Sort(); + dataModeController.UpdateSupportedDataModes(m_SupportedDataModes, dataMode); } protected virtual void OnUpdateSupportedDataModes(List supportedModes) diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index c96b3cbbf..754bc102b 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -73,6 +73,7 @@ private class Content public static readonly GUIContent kAsyncUploadTimeSlice = EditorGUIUtility.TrTextContent("Time Slice", "The amount of time (in milliseconds) Unity spends uploading Texture and Mesh data to the GPU per frame."); public static readonly GUIContent kAsyncUploadBufferSize = EditorGUIUtility.TrTextContent("Buffer Size", "The size (in megabytes) of the upload buffer Unity uses to stream Texture and Mesh data to GPU."); public static readonly GUIContent kAsyncUploadPersistentBuffer = EditorGUIUtility.TrTextContent("Persistent Buffer", "When enabled, the upload buffer persists even when there is nothing left to upload."); + public static readonly GUIContent kAsyncUploadBufferSizeWarning = EditorGUIUtility.TrTextContent("Unity has detected that you are using an upload buffer size of {0} MB with the '{1}' setting enabled. If you have issues with excessive memory usage, you may need to reduce the upload buffer size or disable the '{1}' setting. Memory fragmentation can occur if you choose the latter option."); public static readonly GUIContent kOverrideTerrainPixelError = EditorGUIUtility.TrTextContent("", "Whether to override pixel error in active Terrains."); public static readonly GUIContent kOverrideTerrainBasemapDist = EditorGUIUtility.TrTextContent("", "Whether to override base map distance in active Terrains."); @@ -119,7 +120,8 @@ private class Styles } public const int kMinAsyncRingBufferSize = 2; - public const int kMaxAsyncRingBufferSize = 512; + public const int kMaxAsyncRingBufferSize = 2047; + public const int kAsyncRingBufferSizeWarningThreshold = 513; public const int kMinAsyncUploadTimeSlice = 1; public const int kMaxAsyncUploadTimeSlice = 33; @@ -853,6 +855,12 @@ public override void OnInspectorGUI() asyncUploadTimeSliceProperty.intValue = Mathf.Clamp(asyncUploadTimeSliceProperty.intValue, kMinAsyncUploadTimeSlice, kMaxAsyncUploadTimeSlice); asyncUploadBufferSizeProperty.intValue = Mathf.Clamp(asyncUploadBufferSizeProperty.intValue, kMinAsyncRingBufferSize, kMaxAsyncRingBufferSize); + if (asyncUploadBufferSizeProperty.intValue >= kAsyncRingBufferSizeWarningThreshold && asyncUploadPersistentBufferProperty.boolValue) + { + string messageToDisplay = string.Format(Content.kAsyncUploadBufferSizeWarning.text, asyncUploadBufferSizeProperty.intValue, Content.kAsyncUploadPersistentBuffer.text); + EditorGUILayout.HelpBox(messageToDisplay, MessageType.Warning, false); + } + GUILayout.Space(10); GUILayout.Label(EditorGUIUtility.TempContent("Level of Detail"), EditorStyles.boldLabel); diff --git a/Editor/Mono/Inspector/ReflectionProbeEditor.cs b/Editor/Mono/Inspector/ReflectionProbeEditor.cs index a9eb5df84..150af11d0 100644 --- a/Editor/Mono/Inspector/ReflectionProbeEditor.cs +++ b/Editor/Mono/Inspector/ReflectionProbeEditor.cs @@ -239,7 +239,7 @@ public void OnDisable() private bool IsCollidingWithOtherProbes(string targetPath, ReflectionProbe targetProbe, out ReflectionProbe collidingProbe) { - ReflectionProbe[] probes = FindObjectsOfType().ToArray(); + ReflectionProbe[] probes = FindObjectsByType(FindObjectsSortMode.InstanceID).ToArray(); collidingProbe = null; foreach (var probe in probes) { diff --git a/Editor/Mono/Inspector/RenderPipelineEditorUtility.cs b/Editor/Mono/Inspector/RenderPipelineEditorUtility.cs index 61edf8d30..690db4bc2 100644 --- a/Editor/Mono/Inspector/RenderPipelineEditorUtility.cs +++ b/Editor/Mono/Inspector/RenderPipelineEditorUtility.cs @@ -9,42 +9,27 @@ namespace UnityEditor.Rendering { - [AttributeUsage(AttributeTargets.Class)] - public class ScriptableRenderPipelineExtensionAttribute : Attribute - { - internal Type renderPipelineType; - - public ScriptableRenderPipelineExtensionAttribute(Type renderPipelineAsset) - { - if (!(renderPipelineAsset?.IsSubclassOf(typeof(RenderPipelineAsset)) ?? false)) - throw new ArgumentException("Given renderPipelineAsset must derive from RenderPipelineAsset"); - renderPipelineType = renderPipelineAsset; - } - - public bool inUse - => GraphicsSettings.currentRenderPipeline?.GetType() == renderPipelineType; - } - public static class RenderPipelineEditorUtility { public static Type[] GetDerivedTypesSupportedOnCurrentPipeline() { - return TypeCache.GetTypesDerivedFrom().Where(t => - { - var attribute = t.GetCustomAttribute(); - return attribute != null && attribute.isSupportedOnCurrentPipeline; - - }).ToArray(); + return TypeCache.GetTypesDerivedFrom() + .Where(t => t.GetCustomAttribute() is { isSupportedOnCurrentPipeline: true }) + .ToArray(); } + [Obsolete($"{nameof(FetchFirstCompatibleTypeUsingScriptableRenderPipelineExtension)} is deprecated. Use {nameof(GetDerivedTypesSupportedOnCurrentPipeline)} instead. #from(2023.1)", false)] public static Type FetchFirstCompatibleTypeUsingScriptableRenderPipelineExtension() { var extensionTypes = TypeCache.GetTypesDerivedFrom(); foreach (Type extensionType in extensionTypes) { +#pragma warning disable CS0618 if (Attribute.GetCustomAttribute(extensionType, typeof(ScriptableRenderPipelineExtensionAttribute)) is ScriptableRenderPipelineExtensionAttribute { inUse: true }) return extensionType; +#pragma warning restore CS0618 + } return null; diff --git a/Editor/Mono/Inspector/RendererLightingSettings.cs b/Editor/Mono/Inspector/RendererLightingSettings.cs index 818fba404..6a7588a01 100644 --- a/Editor/Mono/Inspector/RendererLightingSettings.cs +++ b/Editor/Mono/Inspector/RendererLightingSettings.cs @@ -594,24 +594,6 @@ void ShowAtlasGUI(int instanceID, bool isMeshRenderer) GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - bool showProgressiveInfo = !isPreset && settings.bakedGI; - - if (showProgressiveInfo && Unsupported.IsDeveloperMode()) - { - Hash128 instanceHash; - Lightmapping.GetPVRInstanceHash(instanceID, out instanceHash); - EditorGUILayout.LabelField(Styles.pvrInstanceHash, GUIContent.Temp(instanceHash.ToString())); - - Hash128 atlasHash; - Lightmapping.GetPVRAtlasHash(instanceID, out atlasHash); - EditorGUILayout.LabelField(Styles.pvrAtlasHash, GUIContent.Temp(atlasHash.ToString())); - - int atlasInstanceOffset; - Lightmapping.GetPVRAtlasInstanceOffset(instanceID, out atlasInstanceOffset); - EditorGUILayout.LabelField(Styles.pvrAtlasInstanceOffset, GUIContent.Temp(atlasInstanceOffset.ToString())); - } - EditorGUI.indentLevel -= 1; - GUILayout.Space(5); } diff --git a/Editor/Mono/Inspector/ScriptableRenderPipelineExtensionAttribute.deprecated.cs b/Editor/Mono/Inspector/ScriptableRenderPipelineExtensionAttribute.deprecated.cs new file mode 100644 index 000000000..0f6a6bb85 --- /dev/null +++ b/Editor/Mono/Inspector/ScriptableRenderPipelineExtensionAttribute.deprecated.cs @@ -0,0 +1,26 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine.Rendering; + +namespace UnityEditor.Rendering; + +[Obsolete($"{nameof(ScriptableRenderPipelineExtensionAttribute)} is deprecated. Use {nameof(SupportedOnRenderPipelineAttribute)} instead. #from(23.1) (UnityUpgradable) -> UnityEngine.Rendering.SupportedOnRenderPipelineAttribute", false)] +[AttributeUsage(AttributeTargets.Class)] +public class ScriptableRenderPipelineExtensionAttribute : Attribute +{ + internal Type renderPipelineType; + + public ScriptableRenderPipelineExtensionAttribute(Type rpAssetType) + { + if (!(rpAssetType?.IsSubclassOf(typeof(RenderPipelineAsset)) ?? false)) + throw new ArgumentException($"Given {nameof(rpAssetType)} must derive from {nameof(RenderPipelineAsset)}"); + renderPipelineType = rpAssetType; + } + + [Obsolete($"ScriptableRenderPipelineExtensionAttribute.inUse is deprecated. Use SupportedOnRenderPipelineAttribute.isSupportedOnCurrentPipeline instead. #from(23.1) (UnityUpgradable) -> UnityEngine.Rendering.SupportedOnRenderPipelineAttribute.isSupportedOnCurrentPipeline", false)] + public bool inUse + => GraphicsSettings.currentRenderPipelineAssetType == renderPipelineType; +} diff --git a/Editor/Mono/Inspector/Texture3DPreview.cs b/Editor/Mono/Inspector/Texture3DPreview.cs index 0f3edc167..6ca548020 100644 --- a/Editor/Mono/Inspector/Texture3DPreview.cs +++ b/Editor/Mono/Inspector/Texture3DPreview.cs @@ -98,6 +98,7 @@ static class Styles const float s_MinViewDistance = 0f; const float s_MaxViewDistance = 5.0f; static readonly Vector2 s_InitialRotation = new Vector2(15, 30); + const float s_MaxPreviewPixelCount = 512 * 512 * 512; PreviewRenderUtility m_PreviewUtility; Preview3DMode m_Preview3DMode; @@ -440,6 +441,16 @@ void DrawPreview() m_PreviewUtility.Render(); } + bool IsPreviewExpensiveToDisplay() + { + if (m_Preview3DMode == Preview3DMode.Volume || m_Preview3DMode == Preview3DMode.SDF) + { + Vector3 res = GetTextureResolution(Texture); + return res.x * res.y * res.z > s_MaxPreviewPixelCount; + } + return false; + } + public void OnPreviewGUI(Rect r, GUIStyle background) { if (!ShaderUtil.hardwareSupportsRectRenderTexture || !SystemInfo.supports3DTextures) @@ -455,6 +466,12 @@ public void OnPreviewGUI(Rect r, GUIStyle background) EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40), "Compressed 3D texture preview is not supported"); return; } + if (IsPreviewExpensiveToDisplay()) + { + if (Event.current.type == EventType.Repaint) + EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40), "Preview disabled (3D texture too large to display)"); + return; + } InitPreviewUtility(); Event e = Event.current; @@ -487,6 +504,9 @@ public Texture2D RenderStaticPreview(Texture texture, int width, int height) OnEnable(); m_QualityModifier *= 2; + if (IsPreviewExpensiveToDisplay()) + return null; + Rect r = new Rect(0, 0, width, height); m_PreviewUtility.BeginStaticPreview(r); DrawPreview(); diff --git a/Editor/Mono/Inspector/UnityEventDrawer.cs b/Editor/Mono/Inspector/UnityEventDrawer.cs index 44ad0876e..554a4ae23 100644 --- a/Editor/Mono/Inspector/UnityEventDrawer.cs +++ b/Editor/Mono/Inspector/UnityEventDrawer.cs @@ -256,7 +256,7 @@ private SerializedProperty GetArgument(SerializedProperty pListener) public override VisualElement CreatePropertyGUI(SerializedProperty property) { m_Prop = property; - m_Text = property.displayName; + m_Text = preferredLabel; m_DummyEvent = GetDummyEvent(m_Prop); var listViewContainer = new VisualElement(); diff --git a/Editor/Mono/Modules/BeeBuildPostprocessor.cs b/Editor/Mono/Modules/BeeBuildPostprocessor.cs index 954f5f641..ca9580bd9 100644 --- a/Editor/Mono/Modules/BeeBuildPostprocessor.cs +++ b/Editor/Mono/Modules/BeeBuildPostprocessor.cs @@ -168,6 +168,13 @@ IEnumerable GetPluginsFor(BuildTarget target) } } + static IEnumerable GetFilesWithRoleFromBuildReport(BuildReport report, params string[] roles) => + report.GetFiles() + .Where(file => roles.Contains(file.role)) + .Select(file => file.path.ToNPath()) + .GroupBy(file => file.FileName) + .Select(group => group.First()); + LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) { var namedBuildTarget = GetNamedBuildTarget(args); @@ -178,39 +185,45 @@ LinkerConfig LinkerConfigFor(BuildPostProcessArgs args) if (GetScriptingBackend(args) == ScriptingBackend.IL2CPP && strippingLevel == ManagedStrippingLevel.Disabled) strippingLevel = ManagedStrippingLevel.Minimal; - if (strippingLevel > ManagedStrippingLevel.Disabled) - { - var additionalArgs = new List(); - - var diagArgs = Debug.GetDiagnosticSwitch("VMUnityLinkerAdditionalArgs").value as string; - if (!string.IsNullOrEmpty(diagArgs)) - additionalArgs.Add(diagArgs.Trim('\'')); + var additionalArgs = new List(); - var linkerInputDirectory = DagDirectory.Combine($"artifacts/UnityLinkerInputs").CreateDirectory(); - return new LinkerConfig + var diagArgs = Debug.GetDiagnosticSwitch("VMUnityLinkerAdditionalArgs").value as string; + if (!string.IsNullOrEmpty(diagArgs)) + additionalArgs.Add(diagArgs.Trim('\'')); + + var linkerInputDirectory = DagDirectory.Combine($"artifacts/UnityLinkerInputs").CreateDirectory(); + + // In Disabled mode, we pass all generated and engine assemblies to the linker as roots, as the linker + // will only perform a simple assembly reference traversal, ignoring link.xml files and attributes which + // would otherwise find dependent assemblies to preserve. + // In other modes (when stripping is desired), we pass only a smaller set of user assemblies (assemblies from + // packages if used in any scenes, as well as any assembly from the Assets folder) as roots. + var assembliesToProcess = strippingLevel == ManagedStrippingLevel.Disabled + ? GetFilesWithRoleFromBuildReport(args.report, "ManagedLibrary", "ManagedEngineAPI").Select(f => f.FileName) + : args.usedClassRegistry.GetUserAssemblies(); + + return new LinkerConfig + { + LinkXmlFiles = AssemblyStripper.GetLinkXmlFiles(args, linkerInputDirectory), + EditorToLinkerData = AssemblyStripper.WriteEditorData(args, linkerInputDirectory), + AssembliesToProcess = assembliesToProcess.ToArray(), + Runtime = GetScriptingBackend(args).ToString().ToLower(), + Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument( + PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), + Ruleset = strippingLevel switch { - LinkXmlFiles = AssemblyStripper.GetLinkXmlFiles(args, linkerInputDirectory), - EditorToLinkerData = AssemblyStripper.WriteEditorData(args, linkerInputDirectory), - AssembliesToProcess = args.usedClassRegistry.GetUserAssemblies(), - Runtime = GetScriptingBackend(args).ToString().ToLower(), - Profile = IL2CPPUtils.ApiCompatibilityLevelToDotNetProfileArgument( - PlayerSettings.GetApiCompatibilityLevel(namedBuildTarget), args.target), - Ruleset = strippingLevel switch - { - ManagedStrippingLevel.Minimal => "Minimal", - ManagedStrippingLevel.Low => "Conservative", - ManagedStrippingLevel.Medium => "Aggressive", - ManagedStrippingLevel.High => "Experimental", - _ => throw new ArgumentException($"Unhandled {nameof(ManagedStrippingLevel)} value") - }, - AdditionalArgs = additionalArgs.ToArray(), - ModulesAssetPath = $"{BuildPipeline.GetPlaybackEngineDirectory(args.target, 0)}/modules.asset", - AllowDebugging = GetAllowDebugging(args), - PerformEngineStripping = PlayerSettings.stripEngineCode, - }; - } - - return null; + ManagedStrippingLevel.Disabled => "Copy", + ManagedStrippingLevel.Minimal => "Minimal", + ManagedStrippingLevel.Low => "Conservative", + ManagedStrippingLevel.Medium => "Aggressive", + ManagedStrippingLevel.High => "Experimental", + _ => throw new ArgumentException($"Unhandled {nameof(ManagedStrippingLevel)} value") + }, + AdditionalArgs = additionalArgs.ToArray(), + ModulesAssetPath = $"{BuildPipeline.GetPlaybackEngineDirectory(args.target, 0)}/modules.asset", + AllowDebugging = GetAllowDebugging(args), + PerformEngineStripping = PlayerSettings.stripEngineCode, + }; } static bool IsBuildOptionSet(BuildOptions options, BuildOptions flag) => (options & flag) != 0; @@ -375,7 +388,8 @@ static GenerateNativePluginsForAssembliesSettings GetGenerateNativePluginsForAss ApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(GetNamedBuildTarget(args)), InstallIntoBuildsFolder = GetInstallingIntoBuildsFolder(args), GenerateIdeProject = GetCreateSolution(args), - Development = (args.report.summary.options & BuildOptions.Development) == BuildOptions.Development, + Development = (args.options & BuildOptions.Development) == BuildOptions.Development, + NoGUID = (args.options & BuildOptions.NoUniqueIdentifier) == BuildOptions.NoUniqueIdentifier, ScriptingBackend = GetScriptingBackend(args), Architecture = GetArchitecture(args), DataFolder = GetDataFolderFor(args), @@ -391,11 +405,8 @@ static GenerateNativePluginsForAssembliesSettings GetGenerateNativePluginsForAss .Select(e => new StreamingAssetsFile { File = e.src.ToString(), RelativePath = e.dst.ToString() }) .ToArray(), UseNewInputSystem = IsNewInputSystemEnabled(), - ManagedAssemblies = args.report.GetFiles() - .Where(file => file.role == "ManagedLibrary" || file.role == "DependentManagedLibrary" || file.role == "ManagedEngineAPI") - .Select(file => file.path.ToNPath()) - .GroupBy(file => file.FileName) - .Select(group => group.First().ToString()) + ManagedAssemblies = GetFilesWithRoleFromBuildReport(args.report, "ManagedLibrary", "DependentManagedLibrary", "ManagedEngineAPI") + .Select(p => p.ToString()) .ToArray() }; @@ -506,7 +517,6 @@ public BeeBuildPostprocessor() ResultProcessors["IL2CPP_CodeGen"] = PrintStdoutOnErrorProcessor; ResultProcessors["UnityLinker"] = UnityLinkerResultProcessor; ResultProcessors["ExtractUsedFeatures"] = PrintStdoutOnErrorProcessor; - } protected void DefaultResultProcessor(NodeFinishedMessage node, bool printErrors = true, bool printWarnings = true) @@ -569,6 +579,19 @@ void ReportBuildOutputFiles(BuildPostProcessArgs args) var filesOutput = BeeDriverResult.DataFromBuildProgram.Get(); foreach (var outputfile in filesOutput.Files.ToNPaths().Where(f => f.FileExists() && !f.IsSymbolicLink)) args.report.RecordFileAdded(outputfile.ToString(), outputfile.Extension); + + var config = filesOutput.BootConfigArtifact.ToNPath().ReadAllLines(); + var guidKey = "build-guid="; + var guidLine = config.FirstOrDefault(l => l.StartsWith(guidKey)); + if (guidLine != null) + { + var guid = guidLine.Substring(guidKey.Length); + args.report.SetBuildGUID(new GUID(guid)); + } + else + { + args.report.SetBuildGUID(new GUID("00000000000000000000000000000000")); + } } public virtual string PrepareForBuild(BuildOptions options, BuildTarget target) diff --git a/Editor/Mono/Overlays/OverlayAttribute.cs b/Editor/Mono/Overlays/OverlayAttribute.cs index 472241b0f..50209d7ca 100644 --- a/Editor/Mono/Overlays/OverlayAttribute.cs +++ b/Editor/Mono/Overlays/OverlayAttribute.cs @@ -90,7 +90,7 @@ public float defaultHeight public OverlayAttribute() { m_EditorWindowType = null; - m_DefaultDisplay = false; + m_DefaultDisplay = true; m_Id = null; m_DisplayName = null; m_UssName = null; diff --git a/Editor/Mono/Overlays/OverlayResizer.cs b/Editor/Mono/Overlays/OverlayResizer.cs index 9bb742c3a..b01da37b5 100644 --- a/Editor/Mono/Overlays/OverlayResizer.cs +++ b/Editor/Mono/Overlays/OverlayResizer.cs @@ -13,6 +13,7 @@ class OverlayResizerGroup : VisualElement { const float k_CornerSize = 8; const float k_SideSize = 6; + const float k_MinDistanceFromEdge = 10; [Flags] enum Direction @@ -210,6 +211,7 @@ public OverlayResizerGroup(Overlay overlay) overlay.containerChanged += OnOverlayContainerChanged; overlay.layoutChanged += OnOverlayLayoutChanged; + m_Overlay.rootVisualElement.RegisterCallback(OnOverlayGeometryChanged); UpdateResizerVisibility(); } @@ -223,6 +225,11 @@ void OnOverlayContainerChanged(OverlayContainer container) UpdateResizerVisibility(); } + void OnOverlayGeometryChanged(GeometryChangedEvent evt) + { + UpdateResizerVisibility(); + } + bool ContainerCanShowResizer(OverlayResizer resizer) { var container = m_Overlay.container; @@ -273,6 +280,26 @@ void UpdateResizerVisibility() hide |= Mathf.Approximately(m_Overlay.minSize.y, m_Overlay.maxSize.y); } + if (m_Overlay.canvas != null) + { + var canvas = m_Overlay.canvas.rootVisualElement; + var canvasRect = canvas.rect; + var overlayRect = canvas.WorldToLocal(m_Overlay.rootVisualElement.worldBound); + + if (resizer.HasDirection(Direction.Left)) + hide |= overlayRect.xMin <= k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Right)) + hide |= overlayRect.xMax >= canvasRect.xMax - k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Top)) + hide |= overlayRect.yMin <= k_MinDistanceFromEdge; + + if (resizer.HasDirection(Direction.Bottom)) + hide |= overlayRect.yMax >= canvasRect.yMax - k_MinDistanceFromEdge; + } + + resizer.style.display = hide ? DisplayStyle.None : DisplayStyle.Flex; } } diff --git a/Editor/Mono/Overlays/OverlayUtilities.cs b/Editor/Mono/Overlays/OverlayUtilities.cs index 92f9a4e5b..fe4c09d0a 100644 --- a/Editor/Mono/Overlays/OverlayUtilities.cs +++ b/Editor/Mono/Overlays/OverlayUtilities.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using UnityEditor.EditorTools; using UnityEditor.Toolbars; using UnityEngine; @@ -142,11 +143,21 @@ internal static string GetSignificantLettersForIcon(string s) var folders = s.Split('/'); var last = folders[folders.Length - 1]; - var words = last.Trim().Split(' '); + if (string.IsNullOrEmpty(last)) + return string.Empty; + var words = last.Trim().Split(' '); if (words.Length == 1) { - return words[0].Length > 1 ? words[0].Substring(0, 2) : words[0][0].ToString(); + var regex = new Regex(@"[A-Z][^A-Z]*", RegexOptions.Compiled); + var matches = regex.Matches(words[0]); + if (matches == null || matches.Count == 0) + return words[0].Length > 1 ? words[0].Substring(0, 2) : words[0][0].ToString(); + + if (matches.Count == 1) + return matches[0].Length > 1 ? matches[0].Value.Substring(0, 2) : matches[0].Value[0].ToString(); + + return matches[0].Value.Substring(0, 1) + matches[1].Value.Substring(0, 1); } return words[0].Substring(0, 1) + words[1].Substring(0, 1); diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs b/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs index 345e74696..fb9441f8b 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerEventDisplayData.cs @@ -408,6 +408,12 @@ private bool CreateShaderPropertyDisplayInfo(ShaderPropertyType dataType, int ar return true; } + private string GetArrayIndexString(int nameLength, int numOfValues, int currentIndex) + { + int numOfSpaces = nameLength + (FrameDebuggerHelper.CountDigits(numOfValues) - FrameDebuggerHelper.CountDigits(currentIndex)); + return $"{new string(' ', numOfSpaces)}[{currentIndex}]"; + } + private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, string name, ref ShaderPropertyCollection propertyTypeDisplayData, ref ShaderInfo shaderInfo, ref ShaderPropertyDisplayInfo data) { m_StringBuilder.Clear(); @@ -501,7 +507,13 @@ private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, m_StringBuilder.Clear(); data.m_FoldoutString = String.Format(propertyTypeDisplayData.m_Format, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty); for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) - m_StringBuilder.Append(String.Format(propertyTypeDisplayData.m_Format, $"[{k - arrayIndex}]", string.Empty, shaderInfo.m_Vectors[k].m_Value)); + { + int value = shaderInfo.m_Ints[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value).AppendLine(); + } // Remove last linebreak if (m_StringBuilder.Length > 2) @@ -529,7 +541,13 @@ private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, data.m_FoldoutString = m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, $"{name}[{numOfValues}]", stage, string.Empty, string.Empty, string.Empty, string.Empty).ToString(); m_StringBuilder.Clear(); for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) - m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, $"[{k - arrayIndex}]", string.Empty, shaderInfo.m_Floats[k].m_Value); + { + float value = shaderInfo.m_Floats[k].m_Value; + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value).AppendLine(); + } // Remove last linebreak if (numOfValues > 1 && m_StringBuilder.Length > 2) @@ -557,9 +575,13 @@ private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) { Vector4 value = shaderInfo.m_Vectors[k].m_Value; - int numOfSpaces = name.Length + (FrameDebuggerHelper.CountDigits(numOfValues) - FrameDebuggerHelper.CountDigits(k - arrayIndex)); - m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, $"{new string(' ', numOfSpaces)}[{k - arrayIndex}]", string.Empty, value.x, value.y, value.z, value.w); - m_StringBuilder.AppendLine(); + m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, + value.x, + value.y, + value.z, + value.w).AppendLine(); } // Remove last linebreak @@ -597,13 +619,12 @@ private bool GetShaderPropertyData(ShaderPropertyType dataType, int arrayIndex, for (int k = arrayIndex; k < arrayIndex + numOfValues; k++) { Matrix4x4 value = shaderInfo.m_Matrices[k].m_Value; - int numOfSpaces = name.Length + (FrameDebuggerHelper.CountDigits(numOfValues) - FrameDebuggerHelper.CountDigits(k - arrayIndex)); m_StringBuilder.AppendFormat(propertyTypeDisplayData.m_Format, - $"{new string(' ', numOfSpaces)}[{k - arrayIndex}]", string.Empty, value.m00, value.m01, value.m02, value.m03, - string.Empty, string.Empty, value.m10, value.m11, value.m12, value.m13, - string.Empty, string.Empty, value.m20, value.m21, value.m22, value.m23, - string.Empty, string.Empty, value.m30, value.m31, value.m32, value.m33); - m_StringBuilder.AppendLine(); + GetArrayIndexString(name.Length, numOfValues, k - arrayIndex), + string.Empty, value.m00, value.m01, value.m02, value.m03, + string.Empty, string.Empty, value.m10, value.m11, value.m12, value.m13, + string.Empty, string.Empty, value.m20, value.m21, value.m22, value.m23, + string.Empty, string.Empty, value.m30, value.m31, value.m32, value.m33).AppendLine(); } // Remove last linebreak diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs index 84ba201ea..de8d5cda1 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerHelper.cs @@ -129,7 +129,7 @@ private static void SetMaterialProperties( frameDebuggerMaterial.EnableKeyword(ShaderPropertyIDs._TEX2DARRAY); else if (samplerType == TextureDimension.CubeArray) frameDebuggerMaterial.EnableKeyword(ShaderPropertyIDs._CUBEMAP); - + mat.DisableKeyword(ShaderPropertyIDs._MSAA_2); mat.DisableKeyword(ShaderPropertyIDs._MSAA_4); mat.DisableKeyword(ShaderPropertyIDs._MSAA_8); diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs index c8ae23f7d..9e7851a99 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs @@ -162,7 +162,7 @@ internal struct EventDetails internal static readonly GUIStyle s_ArrayFoldoutStyle = new GUIStyle(EditorStyles.foldout) { - margin = new RectOffset(-30, 0, 0, 0), + margin = new RectOffset(-29, 0, 0, 0), }; internal static readonly GUIStyle s_TitleHorizontalStyle = new GUIStyle(EditorStyles.label) diff --git a/Editor/Mono/PlayerSettings.bindings.cs b/Editor/Mono/PlayerSettings.bindings.cs index 68110ea3b..f9535e6ba 100644 --- a/Editor/Mono/PlayerSettings.bindings.cs +++ b/Editor/Mono/PlayerSettings.bindings.cs @@ -258,7 +258,7 @@ public enum NormalMapEncoding DXT5nm = 1 } - internal enum TextureCompressionFormat + public enum TextureCompressionFormat { Unknown = 0, ETC = 1, @@ -1680,6 +1680,14 @@ public static WindowsGamepadBackendHint windowsGamepadBackendHint [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] internal static extern void SetDefaultTextureCompressionFormat(BuildTargetGroup platform, TextureCompressionFormat format); + [NativeMethod("GetTextureCompressionFormats")] + [StaticAccessor("GetPlayerSettings().GetEditorOnly()")] + internal static extern TextureCompressionFormat[] GetTextureCompressionFormatsImpl(BuildTargetGroup platform); + + [NativeMethod("SetTextureCompressionFormats")] + [StaticAccessor("GetPlayerSettings().GetEditorOnlyForUpdate()")] + internal static extern void SetTextureCompressionFormatsImpl(BuildTargetGroup platform, TextureCompressionFormat[] formats); + [FreeFunction("GetPlayerSettings().GetEditorOnly().RecompileScripts")] internal static extern void RecompileScripts(string reason, bool refreshProject = true); diff --git a/Editor/Mono/PlayerSettingsAndroid.bindings.cs b/Editor/Mono/PlayerSettingsAndroid.bindings.cs index 5c3fe0fdd..4004b2ba3 100644 --- a/Editor/Mono/PlayerSettingsAndroid.bindings.cs +++ b/Editor/Mono/PlayerSettingsAndroid.bindings.cs @@ -184,6 +184,20 @@ internal struct AndroidBanner public Texture2D banner; } + [Flags] + public enum AndroidApplicationEntry : uint + { + /// + /// Include entry which derives from Activity + /// - Activity https://developer.android.com/reference/android/app/Activity + /// + Activity = 1 << 0, + /// + /// Include entry which derives from Game Activity https://developer.android.com/games/agdk/game-activity + /// + GameActivity = 1 << 1 + } + // Player Settings is where you define various parameters for the final game that you will build in Unity. Some of these values are used in the Resolution Dialog that launches when you open a standalone game. public partial class PlayerSettings : UnityEngine.Object { @@ -558,9 +572,40 @@ public static extern bool optimizedFramePacing set; } + public static TextureCompressionFormat[] textureCompressionFormats + { + get + { + return GetTextureCompressionFormatsImpl(BuildTargetGroup.Android); + } + set + { + if (value == null || value.Length == 0) + { + throw new ArgumentException($"Android textureCompressionFormats can't be null or empty"); + } + foreach (var format in value) + { + if (format == TextureCompressionFormat.Unknown || format == TextureCompressionFormat.BPTC) + { + throw new ArgumentException($"{format} can't be used as a target texture compression for Android"); + } + } + SetTextureCompressionFormatsImpl(BuildTargetGroup.Android, value); + } + } + // Google Play App Dependencies info. [NativeProperty("AndroidReportGooglePlayAppDependencies", TargetType.Function)] public static extern bool reportGooglePlayAppDependencies { get; set; } + + public static extern AndroidApplicationEntry applicationEntry + { + [NativeMethod("GetAndroidApplicationEntry")] + get; + [NativeMethod("SetAndroidApplicationEntry")] + set; + } } } } diff --git a/Editor/Mono/PlayerSettingsSwitch.bindings.cs b/Editor/Mono/PlayerSettingsSwitch.bindings.cs index 8665319ec..325f6f1b2 100644 --- a/Editor/Mono/PlayerSettingsSwitch.bindings.cs +++ b/Editor/Mono/PlayerSettingsSwitch.bindings.cs @@ -30,26 +30,26 @@ public enum ScreenResolutionBehavior Both = 3 } - // These language names should be match to the name descriptions where in an NMETA file. + // These language names should be match to the name descriptions where in an NMETA file and SwitchBuildUtils.Languages. // And, please notice that you have to increase numSwitchLanguages in EditorOnlyPlayerSettings.h when you add a new language here. public enum Languages { - AmericanEnglish, - BritishEnglish, - Japanese, - French, - German, - LatinAmericanSpanish, - Spanish, - Italian, - Dutch, - CanadianFrench, - Portuguese, - Russian, - SimplifiedChinese, - TraditionalChinese, - Korean, - BrazilianPortuguese, + AmericanEnglish = 0, + BritishEnglish = 1, + Japanese = 2, + French = 3, + German = 4, + LatinAmericanSpanish = 5, + Spanish = 6, + Italian = 7, + Dutch = 8, + CanadianFrench = 9, + Portuguese = 10, + Russian = 11, + SimplifiedChinese = 12, + TraditionalChinese = 13, + Korean = 14, + BrazilianPortuguese = 15, } public enum diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index 1611c44b4..607d7f1b3 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -3575,7 +3575,7 @@ internal static bool DoRemovePrefabInstanceUnusedOverridesDialog(InstanceOverrid { title = titleRemoveUnusedOverrides; message += "\n\n" + msgDetailsWrittenToTheLog; - if (EditorUtility.DisplayDialog(title, message, L10n.Tr("Yes"), L10n.Tr("No"))) + if (EditorUtility.DisplayDialog(title, message, L10n.Tr("Remove"), L10n.Tr("Cancel"))) return true; } else diff --git a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs index cdceeac7e..f7b8f3e26 100644 --- a/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs +++ b/Editor/Mono/PreferencesWindow/PreferencesSettingsProviders.cs @@ -51,6 +51,7 @@ class GeneralProperties public static readonly GUIContent autoSaveScenesBeforeBuilding = EditorGUIUtility.TrTextContent("Auto-save scenes before building"); public static readonly GUIContent scriptChangesDuringPlay = EditorGUIUtility.TrTextContent("Script Changes While Playing"); public static readonly GUIContent editorFont = EditorGUIUtility.TrTextContent("Editor Font"); + public static readonly GUIContent editorTextSharpness = EditorGUIUtility.TrTextContent("Editor Text Sharpness"); public static readonly GUIContent editorSkin = EditorGUIUtility.TrTextContent("Editor Theme"); public static readonly GUIContent[] editorSkinOptions = { EditorGUIUtility.TrTextContent("Light"), EditorGUIUtility.TrTextContent("Dark") }; public static readonly GUIContent hierarchyHeader = EditorGUIUtility.TrTextContent("Hierarchy window"); @@ -193,6 +194,7 @@ private struct GICacheSettings private static SystemLanguage[] m_stableLanguages = { SystemLanguage.English }; private bool m_EnableCompilerMessagesLocalization; + private float m_EditorTextSharpness = 0.0f; private bool m_AllowAlphaNumericHierarchy = false; private PrefabStage.Mode m_DefaultPrefabModeFromHierarchy = PrefabStage.Mode.InContext; private bool m_Create3DObjectsAtOrigin = false; @@ -516,6 +518,8 @@ private void ShowGeneral(string searchContext) } } + m_EditorTextSharpness = EditorGUILayout.Slider(GeneralProperties.editorTextSharpness, m_EditorTextSharpness, -0.5f, 1.0f); + if (InternalEditorUtility.IsGpuDeviceSelectionSupported()) { // Cache gpu devices @@ -1109,6 +1113,9 @@ private void WritePreferences() EditorPrefs.SetString("Editor.kEditorLocale", m_SelectedLanguage); EditorPrefs.SetBool("Editor.kEnableCompilerMessagesLocalization", m_EnableCompilerMessagesLocalization); + EditorPrefs.SetFloat($"EditorTextSharpness_{EditorResources.GetFont(FontDef.Style.Normal).name}", m_EditorTextSharpness); + EditorApplication.RequestRepaintAllTexts(); + EditorPrefs.SetBool("AllowAlphaNumericHierarchy", m_AllowAlphaNumericHierarchy); EditorPrefs.SetInt("DefaultPrefabModeFromHierarchy", (int)m_DefaultPrefabModeFromHierarchy); @@ -1197,6 +1204,7 @@ private void ReadPreferences() m_EnableEditorLocalization = EditorPrefs.GetBool("Editor.kEnableEditorLocalization", true); m_SelectedLanguage = EditorPrefs.GetString("Editor.kEditorLocale", LocalizationDatabase.GetDefaultEditorLanguage().ToString()); m_EnableCompilerMessagesLocalization = EditorPrefs.GetBool("Editor.kEnableCompilerMessagesLocalization", false); + m_EditorTextSharpness = EditorPrefs.GetFloat($"EditorTextSharpness_{EditorResources.GetFont(FontDef.Style.Normal).name}", 0.0f); m_AllowAlphaNumericHierarchy = EditorPrefs.GetBool("AllowAlphaNumericHierarchy", false); m_DefaultPrefabModeFromHierarchy = GetDefaultPrefabModeForHierarchy(); m_ProgressDialogDelay = EditorPrefs.GetFloat("EditorBusyProgressDialogDelay", 3.0f); diff --git a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs index 0c76a3cf0..3af0d35c5 100644 --- a/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs +++ b/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs @@ -665,13 +665,14 @@ internal static GUIContent GetPrefabButtonContent(int instanceID) { GUIContent result; var defaultPrefabMode = PreferencesProvider.GetDefaultPrefabModeForHierarchy(); + var modifierKey = Application.platform == RuntimePlatform.OSXEditor ? "Option" : "Alt"; switch (defaultPrefabMode) { case PrefabStage.Mode.InContext: - result = new GUIContent("", null, $"Open Prefab Asset in context.\nPress modifier key [Alt] to open in isolation."); + result = new GUIContent("", null, $"Open Prefab Asset in context.\nPress the {modifierKey} modifier key to open in isolation."); break; case PrefabStage.Mode.InIsolation: - result = new GUIContent("", null, "Open Prefab Asset in isolation.\nPress modifier key [Alt] to open in context."); + result = new GUIContent("", null, $"Open Prefab Asset in isolation.\nPress the {modifierKey} modifier key to open in context."); break; default: result = new GUIContent(""); diff --git a/Editor/Mono/SceneModeWindows/LightingExplorerExtensionAttribute.deprecated.cs b/Editor/Mono/SceneModeWindows/LightingExplorerExtensionAttribute.deprecated.cs new file mode 100644 index 000000000..19696e062 --- /dev/null +++ b/Editor/Mono/SceneModeWindows/LightingExplorerExtensionAttribute.deprecated.cs @@ -0,0 +1,17 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEditor.Rendering; +using UnityEngine.Rendering; + +namespace UnityEditor; + +[Obsolete($"{nameof(LightingExplorerExtensionAttribute)} is deprecated. Use {nameof(SupportedOnRenderPipelineAttribute)} instead. #from(23.1) (UnityUpgradable) -> UnityEngine.Rendering.SupportedOnRenderPipelineAttribute", false)] +[AttributeUsage(AttributeTargets.Class)] +public class LightingExplorerExtensionAttribute : ScriptableRenderPipelineExtensionAttribute +{ + public LightingExplorerExtensionAttribute(Type renderPipeline) + : base(renderPipeline) {} +} diff --git a/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs b/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs index 8dbd8ca95..43c9bc6f6 100644 --- a/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingExplorerWindow.cs @@ -13,12 +13,6 @@ namespace UnityEditor //Attribute that should be deprecated in 2020.1 //Will be replaced by ScriptableRenderPipelineAttribute //Kept for package compatibility and user SRP compatibility at the moment - [AttributeUsage(AttributeTargets.Class)] - public class LightingExplorerExtensionAttribute : ScriptableRenderPipelineExtensionAttribute - { - public LightingExplorerExtensionAttribute(Type renderPipeline) - : base(renderPipeline) {} - } public interface ILightingExplorerExtension { @@ -92,7 +86,7 @@ void OnSelectionChange() { if (i == (m_TableTabs.Length - 1)) // last tab containing materials { - int[] selectedIds = UnityEngine.Object.FindObjectsOfType().Where((MeshRenderer mr) => { + int[] selectedIds = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.InstanceID).Where((MeshRenderer mr) => { return Selection.instanceIDs.Contains(mr.gameObject.GetInstanceID()); }).SelectMany(meshRenderer => meshRenderer.sharedMaterials).Where((Material m) => { return m != null && (m.globalIlluminationFlags & MaterialGlobalIlluminationFlags.AnyEmissive) != 0; @@ -185,29 +179,23 @@ private void UpdateTabs() } } - private ILightingExplorerExtension GetDefaultLightingExplorerExtension() + ILightingExplorerExtension GetDefaultLightingExplorerExtension() { - if (s_DefaultLightingExplorerExtension == null) - { - s_DefaultLightingExplorerExtension = new DefaultLightingExplorerExtension(); - } - return s_DefaultLightingExplorerExtension; + return s_DefaultLightingExplorerExtension ??= new DefaultLightingExplorerExtension(); } - private ILightingExplorerExtension GetLightExplorerExtension(System.Type currentSRPType) + ILightingExplorerExtension GetLightExplorerExtension(Type currentSRPType) { if (currentSRPType == null) return GetDefaultLightingExplorerExtension(); - Type extensionType = RenderPipelineEditorUtility.FetchFirstCompatibleTypeUsingScriptableRenderPipelineExtension(); - if (extensionType != null) - { - ILightingExplorerExtension extension = (ILightingExplorerExtension)System.Activator.CreateInstance(extensionType); - return extension; - } + var extensionType = RenderPipelineEditorUtility.GetDerivedTypesSupportedOnCurrentPipeline().FirstOrDefault(); + if (extensionType == null) + return GetDefaultLightingExplorerExtension(); + var extension = (ILightingExplorerExtension) Activator.CreateInstance(extensionType); + return extension; // no light explorer extension found for current srp, return the default one - return GetDefaultLightingExplorerExtension(); } } } diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index 31a207d04..75cf25acd 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -504,11 +504,6 @@ void Buttons(Mode selectedMode) else { var settings = Lightmapping.GetLightingSettingsOrDefaultsFallback(); - // Only show Force Stop when using the PathTracer backend - if (settings.bakedGI && GUILayout.Button("Force Stop", GUILayout.Width(Styles.ButtonWidth))) - { - Lightmapping.ForceStop(); - } if (GUILayout.Button("Cancel", GUILayout.Width(Styles.ButtonWidth))) { Lightmapping.Cancel(); @@ -539,142 +534,151 @@ private void DoBakeReflectionProbes() internal static void Summary() { - long totalMemorySize = 0; - int lightmapCount = 0; - Dictionary sizes = new Dictionary(); - bool directionalLightmapsMode = false; - bool shadowmaskMode = false; - foreach (LightmapData ld in LightmapSettings.lightmaps) + bool autoGenerate = Lightmapping.GetLightingSettingsOrDefaultsFallback().autoGenerate; + if (autoGenerate || !Lightmapping.isRunning) { - if (ld.lightmapColor == null) - continue; - lightmapCount++; + long totalMemorySize = 0; + int lightmapCount = 0; + Dictionary sizes = new Dictionary(); + bool directionalLightmapsMode = false; + bool shadowmaskMode = false; + foreach (LightmapData ld in LightmapSettings.lightmaps) + { + if (ld.lightmapColor == null) + continue; + lightmapCount++; - Vector2 texSize = new Vector2(ld.lightmapColor.width, ld.lightmapColor.height); - if (sizes.ContainsKey(texSize)) - sizes[texSize]++; - else - sizes.Add(texSize, 1); + Vector2 texSize = new Vector2(ld.lightmapColor.width, ld.lightmapColor.height); + if (sizes.ContainsKey(texSize)) + sizes[texSize]++; + else + sizes.Add(texSize, 1); - totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.lightmapColor); - if (ld.lightmapDir) - { - totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.lightmapDir); - directionalLightmapsMode = true; + totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.lightmapColor); + if (ld.lightmapDir) + { + totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.lightmapDir); + directionalLightmapsMode = true; + } + if (ld.shadowMask) + { + totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.shadowMask); + shadowmaskMode = true; + } } - if (ld.shadowMask) + StringBuilder sizesString = new StringBuilder(); + sizesString.Append(lightmapCount); + sizesString.Append((directionalLightmapsMode ? " Directional" : " Non-Directional")); + sizesString.Append(" Lightmap"); + if (lightmapCount != 1) sizesString.Append("s"); + if (shadowmaskMode) { - totalMemorySize += TextureUtil.GetStorageMemorySizeLong(ld.shadowMask); - shadowmaskMode = true; + sizesString.Append(" with Shadowmask"); + if (lightmapCount != 1) sizesString.Append("s"); } - } - StringBuilder sizesString = new StringBuilder(); - sizesString.Append(lightmapCount); - sizesString.Append((directionalLightmapsMode ? " Directional" : " Non-Directional")); - sizesString.Append(" Lightmap"); - if (lightmapCount != 1) sizesString.Append("s"); - if (shadowmaskMode) - { - sizesString.Append(" with Shadowmask"); - if (lightmapCount != 1) sizesString.Append("s"); - } - bool first = true; - foreach (var s in sizes) - { - sizesString.Append(first ? ": " : ", "); - first = false; - if (s.Value > 1) + bool first = true; + foreach (var s in sizes) { - sizesString.Append(s.Value); + sizesString.Append(first ? ": " : ", "); + first = false; + if (s.Value > 1) + { + sizesString.Append(s.Value); + sizesString.Append("x"); + } + sizesString.Append(s.Key.x.ToString(CultureInfo.InvariantCulture.NumberFormat)); sizesString.Append("x"); + sizesString.Append(s.Key.y.ToString(CultureInfo.InvariantCulture.NumberFormat)); + sizesString.Append("px"); } - sizesString.Append(s.Key.x.ToString(CultureInfo.InvariantCulture.NumberFormat)); - sizesString.Append("x"); - sizesString.Append(s.Key.y.ToString(CultureInfo.InvariantCulture.NumberFormat)); - sizesString.Append("px"); - } - sizesString.Append(" "); + sizesString.Append(" "); - GUILayout.BeginHorizontal(); + GUILayout.BeginHorizontal(); - GUILayout.BeginVertical(); - GUILayout.Label(sizesString.ToString(), Styles.labelStyle); - GUILayout.EndVertical(); + GUILayout.BeginVertical(); + GUILayout.Label(sizesString.ToString(), Styles.labelStyle); + GUILayout.EndVertical(); - GUILayout.BeginVertical(); - GUILayout.Label(EditorUtility.FormatBytes(totalMemorySize), Styles.labelStyle); - GUILayout.Label((lightmapCount == 0 ? "No Lightmaps" : ""), Styles.labelStyle); - GUILayout.EndVertical(); + GUILayout.BeginVertical(); + GUILayout.Label(EditorUtility.FormatBytes(totalMemorySize), Styles.labelStyle); + GUILayout.Label((lightmapCount == 0 ? "No Lightmaps" : ""), Styles.labelStyle); + GUILayout.EndVertical(); - GUILayout.EndHorizontal(); + GUILayout.EndHorizontal(); + } GUILayout.BeginVertical(); - GUILayout.Label("Occupied Texels: " + InternalEditorUtility.CountToString(Lightmapping.occupiedTexelCount), Styles.labelStyle); - if (Lightmapping.isRunning) + if (autoGenerate) { - int numLightmapsInView = 0; - int numConvergedLightmapsInView = 0; - int numNotConvergedLightmapsInView = 0; + GUILayout.Label("Occupied Texels: " + InternalEditorUtility.CountToString(Lightmapping.occupiedTexelCount), Styles.labelStyle); + if (Lightmapping.isRunning) + { + int numLightmapsInView = 0; + int numConvergedLightmapsInView = 0; + int numNotConvergedLightmapsInView = 0; - int numLightmapsNotInView = 0; - int numConvergedLightmapsNotInView = 0; - int numNotConvergedLightmapsNotInView = 0; + int numLightmapsNotInView = 0; + int numConvergedLightmapsNotInView = 0; + int numNotConvergedLightmapsNotInView = 0; - int numInvalidConvergenceLightmaps = 0; - int numLightmaps = LightmapSettings.lightmaps.Length; - for (int i = 0; i < numLightmaps; ++i) - { - LightmapConvergence lc = Lightmapping.GetLightmapConvergence(i); - if (!lc.IsValid()) + int numInvalidConvergenceLightmaps = 0; + int numLightmaps = LightmapSettings.lightmaps.Length; + for (int i = 0; i < numLightmaps; ++i) { - numInvalidConvergenceLightmaps++; - continue; - } + LightmapConvergence lc = Lightmapping.GetLightmapConvergence(i); + if (!lc.IsValid()) + { + numInvalidConvergenceLightmaps++; + continue; + } - if (Lightmapping.GetVisibleTexelCount(i) > 0) - { - numLightmapsInView++; - if (lc.IsConverged()) - numConvergedLightmapsInView++; + if (Lightmapping.GetVisibleTexelCount(i) > 0) + { + numLightmapsInView++; + if (lc.IsConverged()) + numConvergedLightmapsInView++; + else + numNotConvergedLightmapsInView++; + } else - numNotConvergedLightmapsInView++; + { + numLightmapsNotInView++; + if (lc.IsConverged()) + numConvergedLightmapsNotInView++; + else + numNotConvergedLightmapsNotInView++; + } } - else + if (Lightmapping.atlasCount > 0) { - numLightmapsNotInView++; - if (lc.IsConverged()) - numConvergedLightmapsNotInView++; - else - numNotConvergedLightmapsNotInView++; + int convergedMaps = numConvergedLightmapsInView + numConvergedLightmapsNotInView; + GUILayout.Label("Lightmap convergence: (" + convergedMaps + "/" + Lightmapping.atlasCount + ")", Styles.labelStyle); } + EditorGUILayout.LabelField("Lightmaps in view: " + numLightmapsInView, Styles.labelStyle); + EditorGUI.indentLevel += 1; + EditorGUILayout.LabelField("Converged: " + numConvergedLightmapsInView, Styles.labelStyle); + EditorGUILayout.LabelField("Not Converged: " + numNotConvergedLightmapsInView, Styles.labelStyle); + EditorGUI.indentLevel -= 1; + EditorGUILayout.LabelField("Lightmaps not in view: " + numLightmapsNotInView, Styles.labelStyle); + EditorGUI.indentLevel += 1; + EditorGUILayout.LabelField("Converged: " + numConvergedLightmapsNotInView, Styles.labelStyle); + EditorGUILayout.LabelField("Not Converged: " + numNotConvergedLightmapsNotInView, Styles.labelStyle); + EditorGUI.indentLevel -= 1; + + LightProbesConvergence lpc = Lightmapping.GetLightProbesConvergence(); + if (lpc.IsValid() && lpc.probeSetCount > 0) + GUILayout.Label("Light Probes convergence: (" + lpc.convergedProbeSetCount + "/" + lpc.probeSetCount + ")", Styles.labelStyle); } - if (Lightmapping.atlasCount > 0) - { - int convergedMaps = numConvergedLightmapsInView + numConvergedLightmapsNotInView; - GUILayout.Label("Lightmap convergence: (" + convergedMaps + "/" + Lightmapping.atlasCount + ")", Styles.labelStyle); - } - EditorGUILayout.LabelField("Lightmaps in view: " + numLightmapsInView, Styles.labelStyle); - EditorGUI.indentLevel += 1; - EditorGUILayout.LabelField("Converged: " + numConvergedLightmapsInView, Styles.labelStyle); - EditorGUILayout.LabelField("Not Converged: " + numNotConvergedLightmapsInView, Styles.labelStyle); - EditorGUI.indentLevel -= 1; - EditorGUILayout.LabelField("Lightmaps not in view: " + numLightmapsNotInView, Styles.labelStyle); - EditorGUI.indentLevel += 1; - EditorGUILayout.LabelField("Converged: " + numConvergedLightmapsNotInView, Styles.labelStyle); - EditorGUILayout.LabelField("Not Converged: " + numNotConvergedLightmapsNotInView, Styles.labelStyle); - EditorGUI.indentLevel -= 1; - - LightProbesConvergence lpc = Lightmapping.GetLightProbesConvergence(); - if (lpc.IsValid() && lpc.probeSetCount > 0) - GUILayout.Label("Light Probes convergence: (" + lpc.convergedProbeSetCount + "/" + lpc.probeSetCount + ")", Styles.labelStyle); + float bakeTime = Lightmapping.GetLightmapBakeTimeTotal(); + float mraysPerSec = Lightmapping.GetLightmapBakePerformanceTotal(); + if (mraysPerSec >= 0.0) + GUILayout.Label("Bake Performance: " + mraysPerSec.ToString("0.00", CultureInfo.InvariantCulture.NumberFormat) + " mrays/sec", Styles.labelStyle); } - float bakeTime = Lightmapping.GetLightmapBakeTimeTotal(); - float mraysPerSec = Lightmapping.GetLightmapBakePerformanceTotal(); - if (mraysPerSec >= 0.0) - GUILayout.Label("Bake Performance: " + mraysPerSec.ToString("0.00", CultureInfo.InvariantCulture.NumberFormat) + " mrays/sec", Styles.labelStyle); + if (!Lightmapping.isRunning) { + float bakeTime = Lightmapping.GetLightmapBakeTimeTotal(); float bakeTimeRaw = Lightmapping.GetLightmapBakeTimeRaw(); if (bakeTime >= 0.0) { diff --git a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs index c679494dc..a4426b248 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowEnvironmentTab.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Linq; using Object = UnityEngine.Object; using UnityEngine; using UnityEngine.Rendering; @@ -79,7 +80,7 @@ LightingWindowEnvironmentSection environmentEditor { get { - var currentSRP = GraphicsSettings.currentRenderPipeline?.GetType(); + var currentSRP = GraphicsSettings.currentRenderPipelineAssetType; if (m_EnvironmentSection != null && m_SRP != currentSRP) { m_SRP = currentSRP; @@ -89,9 +90,7 @@ LightingWindowEnvironmentSection environmentEditor if (m_EnvironmentSection == null) { - Type extensionType = RenderPipelineEditorUtility.FetchFirstCompatibleTypeUsingScriptableRenderPipelineExtension(); - if (extensionType == null) - extensionType = typeof(DefaultEnvironmentSectionExtension); + var extensionType = RenderPipelineEditorUtility.GetDerivedTypesSupportedOnCurrentPipeline().FirstOrDefault() ?? typeof(DefaultEnvironmentSectionExtension); LightingWindowEnvironmentSection extension = (LightingWindowEnvironmentSection)Activator.CreateInstance(extensionType); m_EnvironmentSection = extension; m_EnvironmentSection.OnEnable(); @@ -129,7 +128,7 @@ Editor otherRenderingEditor public void OnEnable() { - m_SRP = GraphicsSettings.currentRenderPipeline?.GetType(); + m_SRP = GraphicsSettings.currentRenderPipelineAssetType; m_ShowOtherSettings = new SavedBool($"LightingWindow.ShowOtherSettings", true); } diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs index 1f001c3b9..34ec8049f 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightingTab.cs @@ -32,6 +32,7 @@ class Styles public static readonly GUIContent displayOcclusion = EditorGUIUtility.TrTextContent("Display Occlusion"); public static readonly GUIContent highlightInvalidCells = EditorGUIUtility.TrTextContent("Highlight Invalid Cells", "Highlight the invalid cells that cannot be used for probe interpolation."); public static readonly GUIContent progressiveGPUBakingDevice = EditorGUIUtility.TrTextContent("GPU Baking Device", "Will list all available GPU devices."); + public static readonly GUIContent gpuBakingProfile = EditorGUIUtility.TrTextContent("GPU Baking Profile", "The profile chosen for trading off between performance and memory usage when baking using the GPU."); public static readonly GUIContent progressiveGPUChangeWarning = EditorGUIUtility.TrTextContent("Changing the compute device used by the Progressive GPU Lightmapper requires the editor to be relaunched. Do you want to change device and restart?"); public static readonly GUIContent concurrentJobs = EditorGUIUtility.TrTextContent("Concurrent Jobs", "The amount of simultaneously scheduled jobs."); public static readonly GUIContent progressiveGPUUnknownDeviceInfo = EditorGUIUtility.TrTextContent("No devices found. Please start an initial bake to make this information available."); @@ -50,13 +51,26 @@ class Styles EditorGUIUtility.TrTextContent("Low"), EditorGUIUtility.TrTextContent("High") }; + + // Keep in sync with BakingProfile.h::BakingProfile + public static readonly int bakingProfileDefault = 2; + public static readonly int[] bakingProfileValues = {0, 1, 2, 3, 4}; + public static readonly GUIContent[] bakingProfileStrings = + { + EditorGUIUtility.TrTextContent("Highest Performance"), + EditorGUIUtility.TrTextContent("High Performance"), + EditorGUIUtility.TrTextContent("Balanced"), + EditorGUIUtility.TrTextContent("Low Memory Usage"), + EditorGUIUtility.TrTextContent("Lowest Memory Usage"), + }; } SavedBool m_ShowLightingSettings; SavedBool m_ShowWorkflowSettings; SavedBool m_ShowProbeDebugSettings; Vector2 m_ScrollPosition = Vector2.zero; - string m_LightmappingDeviceIndex = "lightmappingDeviceIndex"; + string m_LightmappingDeviceIndexKey = "lightmappingDeviceIndex"; + string m_BakingProfileKey = "lightmappingBakingProfile"; LightingWindowBakeSettings m_BakeSettings; SerializedObject m_LightmapSettings; @@ -179,7 +193,7 @@ void WorkflowSettingsGUI() GUIContent[] lightmappingDeviceStrings = devicesAndPlatforms.Select(x => new GUIContent(x.name)).ToArray(); int bakingDeviceAndPlatform = -1; - string configDeviceAndPlatform = EditorUserSettings.GetConfigValue(m_LightmappingDeviceIndex); + string configDeviceAndPlatform = EditorUserSettings.GetConfigValue(m_LightmappingDeviceIndexKey); if (configDeviceAndPlatform != null) { bakingDeviceAndPlatform = Int32.Parse(configDeviceAndPlatform); @@ -199,7 +213,7 @@ void WorkflowSettingsGUI() { if (EditorUtility.DisplayDialog("Warning", Styles.progressiveGPUChangeWarning.text, "OK", "Cancel")) { - EditorUserSettings.SetConfigValue(m_LightmappingDeviceIndex, bakingDeviceAndPlatform.ToString()); + EditorUserSettings.SetConfigValue(m_LightmappingDeviceIndexKey, bakingDeviceAndPlatform.ToString()); DeviceAndPlatform selectedDeviceAndPlatform = devicesAndPlatforms[bakingDeviceAndPlatform]; EditorApplication.CloseAndRelaunch(new string[] { "-OpenCL-PlatformAndDeviceIndices", selectedDeviceAndPlatform.platformId.ToString(), selectedDeviceAndPlatform.deviceId.ToString() }); } @@ -215,6 +229,20 @@ void WorkflowSettingsGUI() EditorGUILayout.HelpBox(Styles.progressiveGPUUnknownDeviceInfo.text, MessageType.Info); } + + // Handle the baking profile setting + int bakingProfile = Styles.bakingProfileDefault; + string bakingProfileString = EditorUserSettings.GetConfigValue(m_BakingProfileKey); + if (bakingProfileString != null) + { + if (Int32.TryParse(bakingProfileString, out int bakingProfileStoredValue)) + { + if (bakingProfileStoredValue >= 0 && bakingProfileStoredValue <= 4) + bakingProfile = bakingProfileStoredValue; + } + } + bakingProfile = EditorGUILayout.IntPopup(Styles.gpuBakingProfile, bakingProfile, Styles.bakingProfileStrings, Styles.bakingProfileValues); + EditorUserSettings.SetConfigValue(m_BakingProfileKey, bakingProfile.ToString()); } m_ShowProbeDebugSettings.value = EditorGUILayout.Foldout(m_ShowProbeDebugSettings.value, Styles.lightProbeVisualization, true); diff --git a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs index 2f50599f2..9a8d8d182 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindowLightmapPreviewTab.cs @@ -176,7 +176,6 @@ private void LightmapListGUI(LightmapData[] lightmaps, VisualisationGITexture[] } GUILayout.EndVertical(); - LightmapDebugInfo(i); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(5); @@ -302,159 +301,10 @@ private void DebugInfoSection(LightmapData[] lightmaps) } } - { - Dictionary> gbufferHashToLightmapIndices = new Dictionary>(); - for (int i = 0; i < lightmaps.Length; i++) - { - Hash128 gbufferHash; - if (Lightmapping.GetGBufferHash(i, out gbufferHash)) - { - if (!gbufferHashToLightmapIndices.ContainsKey(gbufferHash)) - gbufferHashToLightmapIndices.Add(gbufferHash, new SortedList()); - - gbufferHashToLightmapIndices[gbufferHash].Add(i, i); - } - } - - float totalGBuffersSize = 0.0f; - float totalLightmapsSize = 0.0f; - - foreach (var entry in gbufferHashToLightmapIndices) - { - Hash128 gbufferHash = entry.Key; - float gbufferDataSize = Lightmapping.GetGBufferMemory(ref gbufferHash); - totalGBuffersSize += gbufferDataSize; - - SortedList lightmapIndices = entry.Value; - foreach (var i in lightmapIndices) - { - LightmapMemory lightmapMemory = Lightmapping.GetLightmapMemory(i.Value); - totalLightmapsSize += lightmapMemory.lightmapDataSizeCPU; - totalLightmapsSize += lightmapMemory.lightmapTexturesSize; - } - } - - string foldoutNameFull = String.Format( - "G-buffers ({0}) | Lightmaps ({1})", - SizeString(totalGBuffersSize), - SizeString(totalLightmapsSize)); - - if (lightmaps.Length > 0) - EditorGUILayout.FoldoutTitlebar(false, new GUIContent(foldoutNameFull), true); - } - - System.UInt64[] dummyCounts = new System.UInt64[0]; - - { - MemLabels labels = Lightmapping.GetLightProbeMemLabels(); - ShowObjectNamesSizesAndCounts("Light Probes", kEditorPrefsLightProbes, labels.labels, labels.sizes, dummyCounts); - } - - { - MemLabels labels = Lightmapping.GetTransmissionTexturesMemLabels(); - ShowObjectNamesSizesAndCounts("Transmission textures", kEditorPrefsTransmissionTextures, labels.labels, labels.sizes, dummyCounts); - } - - { - MemLabels labels = Lightmapping.GetMaterialTexturesMemLabels(); - ShowObjectNamesSizesAndCounts("Albedo/emissive textures", kEditorPrefsMaterialTextures, labels.labels, labels.sizes, dummyCounts); - } - - { - GeoMemLabels labels = Lightmapping.GetGeometryMemory(); - ShowObjectNamesSizesAndCounts("Geometry data", kEditorPrefsGeometryData, labels.labels, labels.sizes, labels.triCounts); - } - - { - // Note: this needs to go last. - // It simply shows all the memory labels that were not explicitly queried after the Lightmapping.ResetExplicitlyShownMemLabels() call. - MemLabels labels = Lightmapping.GetNotShownMemLabels(); - string remainingEntriesFoldoutName = Lightmapping.isProgressiveLightmapperDone ? "Leaks" : "In-flight"; - ShowObjectNamesSizesAndCounts(remainingEntriesFoldoutName, kEditorPrefsInFlight, labels.labels, labels.sizes, dummyCounts); - } - EditorGUILayout.Space(); EditorGUIUtility.labelWidth = oldWidth; } - private void LightmapDebugInfo(int index) - { - if (!showDebugInfo) - return; - - GUILayout.Space(5); - GUILayout.BeginVertical(); - - LightmapConvergence lc = Lightmapping.GetLightmapConvergence(index); - if (lc.IsValid()) - { - ulong occupiedTexels = (ulong)lc.occupiedTexelCount; - if (lc.tilingMode > 0) - { - // Make sure we display the total amount of occupied texels once lightmap is converged - // If tiling is on, and lightmap is not converged, display the occupied texel count in current tile - if (lc.IsConverged()) - { - GUILayout.Label("Occupied: " + InternalEditorUtility.CountToString(occupiedTexels), EditorStyles.miniLabel); - GUILayout.Label("Baked using " + lc.GetTileCount() + " tiles", EditorStyles.miniLabel); - } - else - { - occupiedTexels = (ulong)lc.occupiedTexelCountInCurrentTile; - GUILayout.Label("Occupied (in tile): " + InternalEditorUtility.CountToString(occupiedTexels), EditorStyles.miniLabel); - GUILayout.Label("Baking pass (#tile): " + (lc.tilingPassNum + 1) + "/" + lc.GetTileCount(), EditorStyles.miniLabel); - } - } - else - { - GUILayout.Label("Occupied: " + InternalEditorUtility.CountToString(occupiedTexels), EditorStyles.miniLabel); - } - GUIContent direct = EditorGUIUtility.TrTextContent("Direct: " + lc.minDirectSamples + " / " + lc.maxDirectSamples + " / " + lc.avgDirectSamples + "", "min / max / avg samples per texel"); - GUILayout.Label(direct, EditorStyles.miniLabel); - - GUIContent gi = EditorGUIUtility.TrTextContent("GI: " + lc.minGISamples + " / " + lc.maxGISamples + " / " + lc.avgGISamples + "", "min / max / avg samples per texel"); - GUILayout.Label(gi, EditorStyles.miniLabel); - - GUIContent env = EditorGUIUtility.TrTextContent("Environment: " + lc.minEnvSamples + " / " + lc.maxEnvSamples + " / " + lc.avgEnvSamples + "", "min / max / avg samples per texel"); - GUILayout.Label(env, EditorStyles.miniLabel); - } - else - { - GUILayout.Label("Occupied: N/A", EditorStyles.miniLabel); - GUILayout.Label("Direct: N/A", EditorStyles.miniLabel); - GUILayout.Label("GI: N/A", EditorStyles.miniLabel); - GUILayout.Label("Environment: N/A", EditorStyles.miniLabel); - } - float mraysPerSec = Lightmapping.GetLightmapBakePerformance(index); - if (mraysPerSec >= 0.0) - GUILayout.Label(mraysPerSec.ToString("0.00", CultureInfo.InvariantCulture.NumberFormat) + " mrays/sec", EditorStyles.miniLabel); - else - GUILayout.Label("N/A mrays/sec", EditorStyles.miniLabel); - - GUILayout.EndVertical(); - GUILayout.Space(5); - GUILayout.BeginVertical(); - - LightmapMemory lightmapMemory = Lightmapping.GetLightmapMemory(index); - GUILayout.Label("Lightmap data: " + SizeString(lightmapMemory.lightmapDataSizeCPU), EditorStyles.miniLabel); - - GUIContent lightmapTexturesSizeContent = null; - if (lightmapMemory.lightmapTexturesSize > 0.0f) - lightmapTexturesSizeContent = EditorGUIUtility.TrTextContent("Lightmap textures: " + SizeString(lightmapMemory.lightmapTexturesSize)); - else - lightmapTexturesSizeContent = EditorGUIUtility.TrTextContent("Lightmap textures: N/A", "This lightmap has converged and is not owned by the Progressive Lightmapper anymore."); - GUILayout.Label(lightmapTexturesSizeContent, EditorStyles.miniLabel); - - GUIContent GPUSizeContent = null; - if (lightmapMemory.lightmapDataSizeGPU > 0.0f) - GPUSizeContent = EditorGUIUtility.TrTextContent("GPU memory: " + SizeString(lightmapMemory.lightmapDataSizeGPU)); - else - GPUSizeContent = EditorGUIUtility.TrTextContent("GPU memory: N/A"); - GUILayout.Label(GPUSizeContent, EditorStyles.miniLabel); - - GUILayout.EndVertical(); - } - private void ShowObjectNamesSizesAndCounts(string foldoutName, string editorPrefsName, string[] objectNames, float[] sizes, System.UInt64[] counts) { Debug.Assert(objectNames.Length == sizes.Length); diff --git a/Editor/Mono/SceneView/SceneView.cs b/Editor/Mono/SceneView/SceneView.cs index b45f6b47c..95b87024a 100644 --- a/Editor/Mono/SceneView/SceneView.cs +++ b/Editor/Mono/SceneView/SceneView.cs @@ -3456,7 +3456,7 @@ void CommandsGUI() case EventCommandNames.SelectAll: if (execute) { - var gameObjects = FindObjectsOfType(); + var gameObjects = FindObjectsByType(FindObjectsSortMode.InstanceID); var objs = new List(gameObjects.Length); foreach (var go in gameObjects) if (SceneVisibilityManager.instance.IsSelectable(go)) @@ -3473,7 +3473,9 @@ void CommandsGUI() break; case EventCommandNames.InvertSelection: if (execute) - Selection.objects = FindObjectsOfType().Except(Selection.gameObjects).Where(SceneVisibilityManager.instance.IsSelectable).ToArray(); + { + Selection.objects = FindObjectsByType(FindObjectsSortMode.InstanceID).Except(Selection.gameObjects).Where(SceneVisibilityManager.instance.IsSelectable).ToArray(); + } Event.current.Use(); break; case EventCommandNames.SelectChildren: diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs index 4641e216a..05cc9b12d 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/ExposedReferenceDrawer.cs @@ -149,7 +149,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty prop) ObjectField obj = new ObjectField() { name = kVisualElementName, - label = prop.displayName, + label = preferredLabel, bindingPath = m_Item.exposedPropertyDefault.propertyPath, objectType = typeOfExposedReference, value = m_Item.currentReferenceValue, diff --git a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs index 49ba65313..e83823f4f 100644 --- a/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs +++ b/Editor/Mono/ScriptAttributeGUI/Implementations/PropertyDrawers.cs @@ -33,7 +33,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Float) { - var slider = new Slider(property.displayName, range.min, range.max); + var slider = new Slider(preferredLabel, range.min, range.max); slider.AddToClassList(Slider.alignedFieldUssClassName); slider.bindingPath = property.propertyPath; slider.showInputField = true; @@ -41,7 +41,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) } else if (property.propertyType == SerializedPropertyType.Integer) { - var intSlider = new SliderInt(property.displayName, (int)range.min, (int)range.max); + var intSlider = new SliderInt(preferredLabel, (int)range.min, (int)range.max); intSlider.AddToClassList(SliderInt.alignedFieldUssClassName); intSlider.bindingPath = property.propertyPath; intSlider.showInputField = true; @@ -124,12 +124,12 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "float") { - newField = new FloatField(property.displayName); + newField = new FloatField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.type == "double") { - newField = new DoubleField(property.displayName); + newField = new DoubleField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } } @@ -137,38 +137,38 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "int") { - newField = new IntegerField(property.displayName); + newField = new IntegerField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.type == "long") { - newField = new LongField(property.displayName); + newField = new LongField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } } else if (property.propertyType == SerializedPropertyType.Vector2) { - newField = new Vector2Field(property.displayName); + newField = new Vector2Field(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector2Int) { - newField = new Vector2IntField(property.displayName); + newField = new Vector2IntField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector3) { - newField = new Vector3Field(property.displayName); + newField = new Vector3Field(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector3Int) { - newField = new Vector3IntField(property.displayName); + newField = new Vector3IntField(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } else if (property.propertyType == SerializedPropertyType.Vector4) { - newField = new Vector4Field(property.displayName); + newField = new Vector4Field(preferredLabel); ((BaseField)newField).onValidateValue += OnValidateValue; } @@ -262,7 +262,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.String) { var lines = ((MultilineAttribute)attribute).lines; - var field = new TextField(property.displayName); + var field = new TextField(preferredLabel); field.multiline = true; field.bindingPath = property.propertyPath; field.style.height = EditorGUI.kSingleLineHeight + (lines - 1) * kLineHeight; @@ -284,7 +284,7 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent [CustomPropertyDrawer(typeof(TextAreaAttribute))] internal sealed class TextAreaDrawer : PropertyDrawer { - private const int kLineHeight = 13; + private const int kLineHeight = 15; private static string s_InvalidTypeMessage = L10n.Tr("Use TextAreaDrawer with string."); private Vector2 m_ScrollPosition = new Vector2(); @@ -311,31 +311,39 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten public override VisualElement CreatePropertyGUI(SerializedProperty property) { - if (property.propertyType == SerializedPropertyType.String) + if (property.propertyType != SerializedPropertyType.String) + return new Label(s_InvalidTypeMessage); + + var textAreaAttribute = attribute as TextAreaAttribute; + + // Label + first line + var initialHeight = EditorGUI.kSingleLineHeight + EditorGUI.kSingleLineHeight; + + var minHeight = initialHeight + (textAreaAttribute!.minLines - 1) * kLineHeight; + var maxHeight = initialHeight + (textAreaAttribute!.maxLines - 1) * kLineHeight; + + if (maxHeight < minHeight) + maxHeight = minHeight; + + var textField = new TextField { - var textAreaAttribute = attribute as TextAreaAttribute; - var element = new VisualElement(); - var label = new Label(property.displayName); - var scrollView = new ScrollView(); - var textField = new TextField(); - textField.multiline = true; - var minHeight = EditorGUI.kSingleLineHeight + (textAreaAttribute.minLines - 1) * kLineHeight; - var maxHeight = minHeight; - - element.Add(label); - element.Add(scrollView); - - scrollView.Add(textField); - scrollView.style.minHeight = minHeight; - scrollView.style.maxHeight = maxHeight; - - textField.style.minHeight = minHeight; - textField.bindingPath = property.propertyPath; - - return element; - } + label = preferredLabel, + multiline = true, + style = + { + flexDirection = FlexDirection.Column, + whiteSpace = WhiteSpace.Normal, + minHeight = minHeight, + maxHeight = maxHeight + } + }; - return new Label(s_InvalidTypeMessage); + textField.verticalScrollerVisibility = ScrollerVisibility.Auto; + + textField.style.minHeight = minHeight; + textField.bindingPath = property.propertyPath; + + return textField; } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) @@ -384,7 +392,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Color) { var colorUsage = (ColorUsageAttribute)attribute; - var field = new ColorField(property.displayName); + var field = new ColorField(preferredLabel); field.showAlpha = colorUsage.showAlpha; field.hdr = colorUsage.hdr; field.bindingPath = property.propertyPath; @@ -418,7 +426,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) if (property.propertyType == SerializedPropertyType.Gradient) { var gradientUsage = (GradientUsageAttribute)attribute; - var field = new GradientField(property.displayName); + var field = new GradientField(preferredLabel); field.hdr = gradientUsage.hdr; field.colorSpace = gradientUsage.colorSpace; field.bindingPath = property.propertyPath; @@ -454,12 +462,12 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "float") { - newField = new FloatField(property.displayName); + newField = new FloatField(preferredLabel); ((TextInputBaseField)newField).isDelayed = true; } else if (property.type == "double") { - newField = new DoubleField(property.displayName); + newField = new DoubleField(preferredLabel); ((TextInputBaseField)newField).isDelayed = true; } } @@ -467,18 +475,18 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property) { if (property.type == "int") { - newField = new IntegerField(property.displayName); + newField = new IntegerField(preferredLabel); ((TextInputBaseField)newField).isDelayed = true; } else if (property.type == "long") { - newField = new LongField(property.displayName); + newField = new LongField(preferredLabel); ((TextInputBaseField)newField).isDelayed = true; } } else if (property.propertyType == SerializedPropertyType.String) { - newField = new TextField(property.displayName); + newField = new TextField(preferredLabel); ((TextInputBaseField)newField).isDelayed = true; } diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs index f6103f0cc..5dc8cc3d8 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyDrawer.cs @@ -14,6 +14,7 @@ public abstract class PropertyDrawer : GUIDrawer { internal PropertyAttribute m_Attribute; internal FieldInfo m_FieldInfo; + internal string m_PreferredLabel; // The [[PropertyAttribute]] for the property. Not applicable for custom class drawers. (RO) public PropertyAttribute attribute { get { return m_Attribute; } } @@ -21,6 +22,9 @@ public abstract class PropertyDrawer : GUIDrawer // The reflection FieldInfo for the member this property represents. (RO) public FieldInfo fieldInfo { get { return m_FieldInfo; } } + // The preferred label for this property. + public string preferredLabel => m_PreferredLabel; + internal void OnGUISafe(Rect position, SerializedProperty property, GUIContent label) { ScriptAttributeUtility.s_DrawerStack.Push(this); diff --git a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs index dff46e807..c394f7589 100644 --- a/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs +++ b/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs @@ -29,6 +29,7 @@ internal PropertyDrawer propertyDrawer } internal List decoratorDrawers => m_DecoratorDrawers; + internal bool skipDecoratorDrawers { get; set; } private int m_NestingLevel; @@ -153,7 +154,8 @@ internal bool OnGUI(Rect position, SerializedProperty property, GUIContent label float propHeight = position.height; position.height = 0; - if (m_DecoratorDrawers != null && !isCurrentlyNested) + + if (!skipDecoratorDrawers && m_DecoratorDrawers != null && !isCurrentlyNested) { foreach (DecoratorDrawer decorator in m_DecoratorDrawers) { @@ -302,7 +304,7 @@ public float GetHeight(SerializedProperty property, GUIContent label, bool inclu { float height = 0; - if (m_DecoratorDrawers != null && !isCurrentlyNested) + if (!skipDecoratorDrawers && m_DecoratorDrawers != null && !isCurrentlyNested) foreach (DecoratorDrawer drawer in m_DecoratorDrawers) height += drawer.GetHeight(); diff --git a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs index 6542e04cd..97924da5a 100644 --- a/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs +++ b/Editor/Mono/ScriptAttributeGUI/ScriptAttributeUtility.cs @@ -284,11 +284,11 @@ internal static bool GetTypeFromManagedReferenceFullTypeName(string managedRefer { managedReferenceInstanceType = null; - var parts = managedReferenceFullTypename.Split(' '); - if (parts.Length == 2) + var splitIndex = managedReferenceFullTypename.IndexOf(' '); + if (splitIndex > 0) { - var assemblyPart = parts[0]; - var nsClassnamePart = parts[1]; + var assemblyPart = managedReferenceFullTypename.Substring(0, splitIndex); + var nsClassnamePart = managedReferenceFullTypename.Substring(splitIndex); managedReferenceInstanceType = Type.GetType($"{nsClassnamePart}, {assemblyPart}"); } diff --git a/Editor/Mono/ScriptReloadProperties.cs b/Editor/Mono/ScriptReloadProperties.cs index b57cbd2b1..3bb23a2d9 100644 --- a/Editor/Mono/ScriptReloadProperties.cs +++ b/Editor/Mono/ScriptReloadProperties.cs @@ -67,7 +67,7 @@ private void ManagedStore() EditorGUI_TextEditor_cursorIndex = EditorGUI.s_RecycledEditor.cursorIndex; EditorGUI_TextEditor_selectIndex = EditorGUI.s_RecycledEditor.selectIndex; EditorGUI_TextEditor_controlID = EditorGUI.s_RecycledEditor.controlID; - EditorGUI_TextEditor_hasHorizontalCursorPos = EditorGUI.s_RecycledEditor.hasHorizontalCursorPos; + EditorGUI_TextEditor_hasHorizontalCursorPos = EditorGUI.s_RecycledEditor.hasHorizontalCursor; EditorGUI_TextEditor_scrollOffset = EditorGUI.s_RecycledEditor.scrollOffset; EditorGUI_TextEditor_hasFocus = EditorGUI.s_RecycledEditor.m_HasFocus; EditorGUI_TextEditor_graphicalCursorPos = EditorGUI.s_RecycledEditor.graphicalCursorPos; @@ -77,7 +77,7 @@ private void ManagedStore() EditorGUI_DelayedTextEditor_cursorIndex = EditorGUI.s_DelayedTextEditor.cursorIndex; EditorGUI_DelayedTextEditor_selectIndex = EditorGUI.s_DelayedTextEditor.selectIndex; EditorGUI_DelayedTextEditor_controlID = EditorGUI.s_DelayedTextEditor.controlID; - EditorGUI_DelayedTextEditor_hasHorizontalCursorPos = EditorGUI.s_DelayedTextEditor.hasHorizontalCursorPos; + EditorGUI_DelayedTextEditor_hasHorizontalCursorPos = EditorGUI.s_DelayedTextEditor.hasHorizontalCursor; EditorGUI_DelayedTextEditor_scrollOffset = EditorGUI.s_DelayedTextEditor.scrollOffset; EditorGUI_DelayedTextEditor_hasFocus = EditorGUI.s_DelayedTextEditor.m_HasFocus; EditorGUI_DelayedTextEditor_graphicalCursorPos = EditorGUI.s_DelayedTextEditor.graphicalCursorPos; @@ -93,7 +93,7 @@ private void ManagedLoad() EditorGUI.s_RecycledEditor.cursorIndex = EditorGUI_TextEditor_cursorIndex; EditorGUI.s_RecycledEditor.selectIndex = EditorGUI_TextEditor_selectIndex; EditorGUI.s_RecycledEditor.controlID = EditorGUI_TextEditor_controlID; - EditorGUI.s_RecycledEditor.hasHorizontalCursorPos = EditorGUI_TextEditor_hasHorizontalCursorPos; + EditorGUI.s_RecycledEditor.hasHorizontalCursor = EditorGUI_TextEditor_hasHorizontalCursorPos; EditorGUI.s_RecycledEditor.scrollOffset = EditorGUI_TextEditor_scrollOffset; EditorGUI.s_RecycledEditor.m_HasFocus = EditorGUI_TextEditor_hasFocus; EditorGUI.s_RecycledEditor.graphicalCursorPos = EditorGUI_TextEditor_graphicalCursorPos; @@ -103,7 +103,7 @@ private void ManagedLoad() EditorGUI.s_DelayedTextEditor.cursorIndex = EditorGUI_DelayedTextEditor_cursorIndex; EditorGUI.s_DelayedTextEditor.selectIndex = EditorGUI_DelayedTextEditor_selectIndex; EditorGUI.s_DelayedTextEditor.controlID = EditorGUI_DelayedTextEditor_controlID; - EditorGUI.s_DelayedTextEditor.hasHorizontalCursorPos = EditorGUI_DelayedTextEditor_hasHorizontalCursorPos; + EditorGUI.s_DelayedTextEditor.hasHorizontalCursor = EditorGUI_DelayedTextEditor_hasHorizontalCursorPos; EditorGUI.s_DelayedTextEditor.scrollOffset = EditorGUI_DelayedTextEditor_scrollOffset; EditorGUI.s_DelayedTextEditor.m_HasFocus = EditorGUI_DelayedTextEditor_hasFocus; EditorGUI.s_DelayedTextEditor.graphicalCursorPos = EditorGUI_DelayedTextEditor_graphicalCursorPos; diff --git a/Editor/Mono/SerializedObject.bindings.cs b/Editor/Mono/SerializedObject.bindings.cs index dfb176f48..49e18c5a1 100644 --- a/Editor/Mono/SerializedObject.bindings.cs +++ b/Editor/Mono/SerializedObject.bindings.cs @@ -208,6 +208,12 @@ internal extern InspectorMode inspectorMode set; } + internal extern DataMode inspectorDataMode + { + get; + set; + } + // Does the serialized object represents multiple objects due to multi-object editing? (RO) public extern bool isEditingMultipleObjects { diff --git a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs index b0239880f..c51697d85 100644 --- a/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs +++ b/Editor/Mono/SettingsWindow/GraphicsSettingsEditors.cs @@ -296,6 +296,8 @@ internal partial class Styles public static readonly GUIContent shaderPreloadSave = EditorGUIUtility.TrTextContent("Save to asset...", "Save currently tracked shaders into a Shader Variant Manifest asset."); public static readonly GUIContent shaderPreloadClear = EditorGUIUtility.TrTextContent("Clear", "Clear currently tracked shader variant information."); + + public static readonly GUIContent cullingSettings = EditorGUIUtility.TrTextContent("Culling Settings"); } } } diff --git a/Editor/Mono/Tutorial/Highlighter.bindings.cs b/Editor/Mono/Tutorial/Highlighter.bindings.cs index a3dba272a..421b68fdd 100644 --- a/Editor/Mono/Tutorial/Highlighter.bindings.cs +++ b/Editor/Mono/Tutorial/Highlighter.bindings.cs @@ -32,8 +32,8 @@ public static extern string activeText [NativeProperty("s_ActiveVisible", false, TargetType.Field)] public static extern bool activeVisible { get; private set; } - [NativeProperty("s_ActiveIsImguiContainer", false, TargetType.Field)] - internal static extern bool activeIsImguiContainer { get; private set; } + [NativeProperty("s_UseUIToolkitScrolling", false, TargetType.Field)] + internal static extern bool useUIToolkitScrolling { get; private set; } [NativeProperty("k_Padding", false, TargetType.Field)] internal static extern float padding { get; } diff --git a/Editor/Mono/Tutorial/Highlighter.cs b/Editor/Mono/Tutorial/Highlighter.cs index cbbb70854..0aa0cc6fd 100644 --- a/Editor/Mono/Tutorial/Highlighter.cs +++ b/Editor/Mono/Tutorial/Highlighter.cs @@ -29,11 +29,14 @@ public partial class Highlighter private const float kPulseSpeed = 0.45f; // Pulses per second private const float kPopupDuration = 0.33f; // How long time in seconds the popup takes private const int kExpansionMovementSize = 5; // How many pixels the rect expands out in the pulsing movement + // Twice the IMGUI speed because UIToolkit ScrollView scroll offset has a frame delay between updates + private static readonly float kUIToolkitScrollSpeed = scrollSpeed * 2; private static bool s_RecursionLock = false; private static VisualElement activeElement = null; private static ScrollView activeScrollView = null; + private static bool activeIsImgui = false; private static GUIStyle s_HighlightStyle; private static GUIStyle highlightStyle @@ -54,8 +57,9 @@ public static void Stop() activeVisible = false; activeText = string.Empty; activeElement = null; - activeIsImguiContainer = false; + activeIsImgui = false; activeScrollView = null; + useUIToolkitScrolling = true; activeRect = new Rect(); searchMode = HighlightSearchMode.None; @@ -148,7 +152,7 @@ private static void Update() // If view's actualView has changed, we might still find a property with the right name in the other view, // but it's not the one we're looking for. - var elementHidden = activeElement != null && (!activeElement.isHierarchyDisplayed || activeElement.panel == null); + var elementHidden = activeElement != null && (!activeElement.areAncestorsAndSelfDisplayed || activeElement.panel == null); if (activeRect.width == 0 || !ViewWindowIsActive() || elementHidden) { EditorApplication.update -= Update; @@ -165,7 +169,7 @@ private static void Update() Search(); } - if (isUIToolkitWindow) + if (useUIToolkitScrolling) HandleScroll(); // Keep elapsed time explicitly rather than measuring time since highlight began. @@ -246,7 +250,7 @@ private static bool Search() { searchMode = s_SearchMode; - if (isUIToolkitWindow && !activeIsImguiContainer) + if (isUIToolkitWindow && !activeIsImgui) { var found = activeElement != null; if (!found) @@ -261,15 +265,15 @@ private static bool Search() return true; } - activeIsImguiContainer = true; + activeIsImgui = true; } // Try IMGUI s_View.RepaintImmediately(); if (searchMode == HighlightSearchMode.None) { - if (!activeIsImguiContainer) - return true; // Success - control was found in pure Imgui window + if (activeIsImgui && !useUIToolkitScrolling) + return true; // Success - control was found in window that doesn't use UIToolkit ScrollView if (activeElement != null) return true; // Already found the IMGUIContainer @@ -280,10 +284,8 @@ private static bool Search() r.y -= windowPos.y; activeElement = SearchIMGUIContainer(s_ViewWindow.rootVisualElement, r.center); - if (activeElement != null) - return true; - - activeIsImguiContainer = false; + useUIToolkitScrolling = activeScrollView != null; + return true; } s_SearchMode = HighlightSearchMode.None; @@ -358,7 +360,7 @@ private static void HandleScroll() var paddedRect = new Rect(r.x - padding, r.y - padding, r.width + padding * 2, r.height + padding * 2); - activeVisible = !ScrollTowardsRect(activeScrollView, paddedRect, scrollSpeed); + activeVisible = !ScrollTowardsRect(activeScrollView, paddedRect, kUIToolkitScrollSpeed); } else { diff --git a/Editor/Mono/UIElements/Controls/ColorField.cs b/Editor/Mono/UIElements/Controls/ColorField.cs index a8d7e3ad7..654eb3cba 100644 --- a/Editor/Mono/UIElements/Controls/ColorField.cs +++ b/Editor/Mono/UIElements/Controls/ColorField.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; using UnityEngine; using UnityEngine.UIElements; @@ -12,6 +13,10 @@ namespace UnityEditor.UIElements /// public class ColorField : BaseField { + internal static readonly DataBindingProperty showEyeDropperProperty = nameof(showEyeDropper); + internal static readonly DataBindingProperty showAlphaProperty = nameof(showAlpha); + internal static readonly DataBindingProperty hdrProperty = nameof(hdr); + /// /// Instantiates a using the data read from a UXML file. /// @@ -45,39 +50,50 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// If true, the color picker will show the eyedropper control. If false, the color picker won't show the eyedropper control. /// + [CreateProperty] public bool showEyeDropper { get => m_ShowEyeDropper; set { + var previous = m_ShowEyeDropper; m_ShowEyeDropper = value; if (m_EyeDropperElement != null) m_EyeDropperElement.style.display = m_ShowEyeDropper ? DisplayStyle.Flex : DisplayStyle.None; + + if (previous != m_ShowEyeDropper) + NotifyPropertyChanged(showEyeDropperProperty); } } /// /// If true, allows the user to set an alpha value for the color. If false, hides the alpha component. /// + [CreateProperty] public bool showAlpha { get => m_ShowAlpha; set { + var previous = m_ShowAlpha; m_ShowAlpha = value; if (m_AlphaElement != null) m_AlphaElement.style.display = m_ShowAlpha ? DisplayStyle.Flex : DisplayStyle.None; + if (previous != m_ShowAlpha) + NotifyPropertyChanged(showAlphaProperty); } } /// /// If true, treats the color as an HDR value. If false, treats the color as a standard LDR value. /// + [CreateProperty] public bool hdr { get => m_HDR; set { + var previous = m_HDR; m_HDR = value; if (m_HDRLabel != null) m_HDRLabel.style.display = m_HDR ? DisplayStyle.Flex : DisplayStyle.None; @@ -86,6 +102,9 @@ public bool hdr if (m_AlphaGradientContainer != null) m_AlphaGradientContainer.style.display = m_HDR ? DisplayStyle.Flex : DisplayStyle.None; UpdateColorProperties(this.value); + + if (previous != m_HDR) + NotifyPropertyChanged(hdrProperty); } } diff --git a/Editor/Mono/UIElements/Controls/CurveField.cs b/Editor/Mono/UIElements/Controls/CurveField.cs index 83cc6c5b3..b2495c3f4 100644 --- a/Editor/Mono/UIElements/Controls/CurveField.cs +++ b/Editor/Mono/UIElements/Controls/CurveField.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; using UnityEngine; using UnityEditorInternal; using UnityEngine.UIElements; @@ -16,6 +17,8 @@ namespace UnityEditor.UIElements /// public class CurveField : BaseField { + internal static readonly DataBindingProperty renderModeProperty = nameof(renderMode); + /// /// Instantiates a using the data read from a UXML file. /// @@ -89,6 +92,7 @@ public enum RenderMode /// /// The RenderMode of CurveField. The default is RenderMode.Default. /// + [CreateProperty] public RenderMode renderMode { get { return m_RenderMode; } @@ -124,6 +128,7 @@ public RenderMode renderMode } m_TextureDirty = true; + NotifyPropertyChanged(renderModeProperty); } } } @@ -159,6 +164,7 @@ public override AnimationCurve value evt.elementTarget = this; SetValueWithoutNotify(value); SendEvent(evt); + NotifyPropertyChanged(valueProperty); } } else diff --git a/Editor/Mono/UIElements/Controls/GradientField.cs b/Editor/Mono/UIElements/Controls/GradientField.cs index 48dd41f48..3c5d976c4 100644 --- a/Editor/Mono/UIElements/Controls/GradientField.cs +++ b/Editor/Mono/UIElements/Controls/GradientField.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; using UnityEngine; using UnityEditorInternal; using UnityEngine.UIElements; @@ -15,6 +16,9 @@ namespace UnityEditor.UIElements /// public class GradientField : BaseField { + internal static readonly DataBindingProperty colorSpaceProperty = nameof(colorSpace); + internal static readonly DataBindingProperty hdrProperty = nameof(hdr); + static readonly GradientColorKey k_WhiteKeyBegin = new GradientColorKey(Color.white, 0); static readonly GradientColorKey k_WhiteKeyEnd = new GradientColorKey(Color.white, 1); static readonly GradientAlphaKey k_AlphaKeyBegin = new GradientAlphaKey(1, 0); @@ -53,19 +57,47 @@ public override Gradient value evt.elementTarget = this; SetValueWithoutNotify(value); SendEvent(evt); + NotifyPropertyChanged(valueProperty); } } } } + private ColorSpace m_ColorSpace; + /// /// The color space currently used by the field. /// - public ColorSpace colorSpace { get; set; } + [CreateProperty] + public ColorSpace colorSpace + { + get => m_ColorSpace; + set + { + if (m_ColorSpace == value) + return; + m_ColorSpace = value; + NotifyPropertyChanged(colorSpaceProperty); + } + } + + private bool m_Hdr; + /// /// If true, treats the color as an HDR value. If false, treats the color as a standard LDR value. /// - public bool hdr { get; set; } + [CreateProperty] + public bool hdr + { + get => m_Hdr; + set + { + if (m_Hdr == value) + return; + m_Hdr = value; + NotifyPropertyChanged(hdrProperty); + } + } internal static Gradient GradientCopy(Gradient other) { diff --git a/Editor/Mono/UIElements/Controls/MaskField.cs b/Editor/Mono/UIElements/Controls/MaskField.cs index a5b30dd2d..2e97a9233 100644 --- a/Editor/Mono/UIElements/Controls/MaskField.cs +++ b/Editor/Mono/UIElements/Controls/MaskField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using System.Text; using UnityEngine.Pool; using UnityEngine.UIElements; @@ -15,6 +16,8 @@ namespace UnityEditor.UIElements /// public abstract class BaseMaskField : BasePopupField { + internal static readonly DataBindingProperty choicesMasksProperty = nameof(choicesMasks); + internal abstract TChoice MaskToValue(int newMask); internal abstract int ValueToMask(TChoice value); @@ -126,6 +129,7 @@ public override List choices // Make sure to update the text displayed SetValueWithoutNotify(rawValue); + NotifyPropertyChanged(choicesProperty); } } @@ -142,6 +146,7 @@ internal virtual string GetEverythingName() /// /// The list of list of masks for every specific choice to display in the popup menu. /// + [CreateProperty] public virtual List choicesMasks { get { return m_UserChoicesMasks; } @@ -167,6 +172,7 @@ public virtual List choicesMasks // Make sure to update the text displayed SetValueWithoutNotify(rawValue); } + NotifyPropertyChanged(choicesMasksProperty); } } @@ -543,7 +549,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext string choicesFromBag = m_MaskChoices.GetValueFromBag(bag, cc); - var listOfChoices = ParseChoiceList(choicesFromBag); + var listOfChoices = UxmlUtility.ParseStringListAttribute(choicesFromBag); if (listOfChoices != null && listOfChoices.Count > 0) { diff --git a/Editor/Mono/UIElements/Controls/ObjectField.cs b/Editor/Mono/UIElements/Controls/ObjectField.cs index ad9557a53..8550e0052 100644 --- a/Editor/Mono/UIElements/Controls/ObjectField.cs +++ b/Editor/Mono/UIElements/Controls/ObjectField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; @@ -15,6 +16,9 @@ namespace UnityEditor.UIElements /// public class ObjectField : BaseField { + internal static readonly DataBindingProperty objectTypeProperty = nameof(objectType); + internal static readonly DataBindingProperty allowSceneObjectsProperty = nameof(allowSceneObjects); + /// /// Instantiates an using the data read from a UXML file. /// @@ -61,6 +65,7 @@ public override void SetValueWithoutNotify(Object newValue) /// /// The type of the objects that can be assigned. /// + [CreateProperty] public Type objectType { get { return m_objectType; } @@ -70,6 +75,7 @@ public Type objectType { m_objectType = value; UpdateDisplay(); + NotifyPropertyChanged(objectTypeProperty); } } } @@ -79,10 +85,23 @@ internal void SetObjectTypeWithoutDisplayUpdate(Type type) m_objectType = type; } + private bool m_AllowSceneObjects; + /// /// Allows scene objects to be assigned to the field. /// - public bool allowSceneObjects { get; set; } + [CreateProperty] + public bool allowSceneObjects + { + get => m_AllowSceneObjects; + set + { + if (m_AllowSceneObjects == value) + return; + m_AllowSceneObjects = value; + NotifyPropertyChanged(allowSceneObjectsProperty); + } + } protected override void UpdateMixedValueContent() { diff --git a/Editor/Mono/UIElements/Controls/PropertyField.cs b/Editor/Mono/UIElements/Controls/PropertyField.cs index 52963bbe6..e289233a8 100644 --- a/Editor/Mono/UIElements/Controls/PropertyField.cs +++ b/Editor/Mono/UIElements/Controls/PropertyField.cs @@ -261,6 +261,7 @@ void Reset(SerializedProperty newProperty) { if (handler.hasPropertyDrawer) { + handler.propertyDrawer.m_PreferredLabel = label ?? serializedProperty.localizedDisplayName; customPropertyGUI = handler.propertyDrawer.CreatePropertyGUI(m_SerializedProperty); if (customPropertyGUI == null) @@ -299,7 +300,7 @@ private void ResetDecoratorDrawers(PropertyHandler handler) { var decorators = handler.decoratorDrawers; - if (decorators == null || decorators.Count == 0) + if (decorators == null || decorators.Count == 0 || m_DrawNestingLevel > 0) { if (m_DecoratorDrawersContainer != null) { @@ -391,6 +392,9 @@ private VisualElement CreatePropertyIMGUIContainer() var handler = ScriptAttributeUtility.GetHandler(serializedProperty); using (var nestingContext = handler.ApplyNestingContext(m_DrawNestingLevel)) { + // Decorator drawers are already handled on the uitk side + handler.skipDecoratorDrawers = true; + if (label == null) { EditorGUILayout.PropertyField(serializedProperty, true); diff --git a/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs b/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs index e27992a6f..068295c99 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/SearchFieldBase.cs @@ -2,6 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System.Collections.Generic; +using Unity.Properties; using UnityEngine; using UnityEngine.UIElements; @@ -13,6 +15,8 @@ namespace UnityEditor.UIElements public abstract class SearchFieldBase : VisualElement, INotifyValueChanged where TextInputType : TextInputBaseField, new() { + internal static readonly DataBindingProperty valueProperty = nameof(value); + private readonly Button m_SearchButton; private readonly Button m_CancelButton; private readonly TextInputType m_TextField; @@ -35,10 +39,17 @@ protected Button searchButton /// /// If the new value is different from the current value, this method notifies registered callbacks with a . /// + [CreateProperty] public T value { get { return m_TextField.value; } - set { m_TextField.value = value; } + set + { + var previous = m_TextField.value; + m_TextField.value = value; + if (!EqualityComparer.Default.Equals(m_TextField.value, previous)) + NotifyPropertyChanged(valueProperty); + } } /// diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs index c100eac81..2206ca7d5 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarMenu.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; using UnityEngine.UIElements; namespace UnityEditor.UIElements @@ -11,6 +12,9 @@ namespace UnityEditor.UIElements /// public class ToolbarMenu : TextElement, IToolbarMenuElement { + internal static readonly DataBindingProperty menuProperty = nameof(menu); + internal static readonly DataBindingProperty variantProperty = nameof(variant); + /// /// Instantiates a using the data read from a UXML file. /// @@ -38,6 +42,7 @@ public enum Variant /// /// The menu. /// + [CreateProperty(ReadOnly = true)] public DropdownMenu menu { get; } public override string text { @@ -93,13 +98,18 @@ public ToolbarMenu() /// /// The display styles that you can use when creating menus. /// + [CreateProperty] public Variant variant { get { return m_Variant; } set { + var previous = m_Variant; m_Variant = value; EnableInClassList(popupVariantUssClassName, value == Variant.Popup); + + if (previous != m_Variant) + NotifyPropertyChanged(variantProperty); } } } diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs index 64e9155d1..a83a10d38 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarPopupSearchField.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; using UnityEngine.UIElements; namespace UnityEditor.UIElements @@ -11,6 +12,8 @@ namespace UnityEditor.UIElements /// public class ToolbarPopupSearchField : ToolbarSearchField, IToolbarMenuElement { + internal static readonly DataBindingProperty menuProperty = nameof(menu); + /// /// Instantiates a using the data read from a UXML file. /// @@ -28,6 +31,7 @@ public class ToolbarPopupSearchField : ToolbarSearchField, IToolbarMenuElement /// /// The menu used by the pop-up search field element. /// + [CreateProperty(ReadOnly = true)] public DropdownMenu menu { get; } /// diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs index 61f99f5e3..afe757cca 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSearchField.cs @@ -43,26 +43,6 @@ public class ToolbarSearchField : SearchFieldBase get { return base.searchButton; } } - /// - /// The object currently being exposed by the field. - /// - /// - /// If the new value is different from the current value, this method notifies registered callbacks with a of type string. - /// - public new string value - { - get { return base.value; } - set { base.value = value; } - } - - /// - /// Sets the value for the toolbar search field without sending a change event. - /// - public override void SetValueWithoutNotify(string newValue) - { - base.SetValueWithoutNotify(newValue); - } - // KEEP ABOVE CODE TO BE BACKWARD COMPATIBLE WITH 2019.1 ToolbarSearchField /// diff --git a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs index 4d1b19cf6..200289f88 100644 --- a/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs +++ b/Editor/Mono/UIElements/Controls/Toolbar/ToolbarSpacer.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; using UnityEngine.UIElements; namespace UnityEditor.UIElements @@ -12,6 +13,8 @@ namespace UnityEditor.UIElements /// public class ToolbarSpacer : VisualElement { + internal static readonly DataBindingProperty flexProperty = nameof(flex); + /// /// Instantiates a using the data read from a UXML file. /// @@ -45,6 +48,7 @@ public ToolbarSpacer() /// /// Return true if the spacer stretches or shrinks to occupy available space. /// + [CreateProperty] public bool flex { get { return ClassListContains(flexibleSpacerVariantUssClassName); } @@ -53,6 +57,7 @@ public bool flex if (flex != value) { EnableInClassList(flexibleSpacerVariantUssClassName, value); + NotifyPropertyChanged(flexProperty); } } } diff --git a/Editor/Mono/UIElements/Inspector/InspectorElement.cs b/Editor/Mono/UIElements/Inspector/InspectorElement.cs index fc2780255..e54b2068f 100644 --- a/Editor/Mono/UIElements/Inspector/InspectorElement.cs +++ b/Editor/Mono/UIElements/Inspector/InspectorElement.cs @@ -385,7 +385,11 @@ protected override void ExecuteDefaultActionAtTarget(EventBase evt) private Editor GetOrCreateEditor(SerializedObject serializedObject) { - var target = serializedObject?.targetObject; + Object target = null; + if (serializedObject != null && serializedObject.m_NativeObjectPtr != IntPtr.Zero) { + target = serializedObject.targetObject; + } + if (editor != null) { @@ -411,7 +415,10 @@ private Editor GetOrCreateEditor(SerializedObject serializedObject) RegisterCallback(OnDetachFromPanel); - var ed = Editor.CreateEditor(serializedObject?.targetObject); + if (serializedObject != null && serializedObject.m_NativeObjectPtr != IntPtr.Zero) { + target = serializedObject.targetObject; + } + var ed = Editor.CreateEditor(target); editor = ed; ownsEditor = true; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs index 2fc0c98cc..db2e551bf 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Builder.cs @@ -8,6 +8,16 @@ namespace Unity.UI.Builder { class Builder : BuilderPaneWindow, IBuilderViewportWindow, IHasCustomMenu { + static Builder() + { + EditorApplication.fileMenuSaved += () => + { + var builder = ActiveWindow; + if (builder != null && builder.document.hasUnsavedChanges) + builder.SaveChanges(); + }; + } + BuilderSelection m_Selection; BuilderToolbar m_Toolbar; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUSS.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUSS.cs index 406593fe0..a7135828a 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUSS.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUSS.cs @@ -173,5 +173,31 @@ bool WriteUSSToFile(string ussPath, string ussText) return BuilderAssetUtilities.WriteTextFileToDisk(ussPath, ussText); } + + public int GetComplexSelectorsCount() + { + if (m_StyleSheet == null || m_StyleSheet.complexSelectors == null) + return 0; + + var nbComplexSelectorsCount = 0; + for (var complexSelectorIndex = 0; + complexSelectorIndex < m_StyleSheet.complexSelectors.Length; + ++complexSelectorIndex) + { + var complexSelector = m_StyleSheet.complexSelectors[complexSelectorIndex]; + + // Omit special selection rule. + if (complexSelector.selectors.Length > 0 && + complexSelector.selectors[0].parts.Length > 0 && + (complexSelector.selectors[0].parts[0].value == BuilderConstants.SelectedStyleSheetSelectorName + || complexSelector.selectors[0].parts[0].value + .StartsWith(BuilderConstants.StyleSelectorElementName))) + continue; + + nbComplexSelectorsCount++; + } + + return nbComplexSelectorsCount; + } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs index 08ca0d21a..e850f1729 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Document/BuilderDocumentOpenUXML.cs @@ -57,7 +57,7 @@ class BuilderDocumentOpenUXML public BuilderUXMLFileSettings fileSettings => m_FileSettings ?? (m_FileSettings = new BuilderUXMLFileSettings(visualTreeAsset)); - List openUXMLFiles + internal List openUXMLFiles { get { @@ -376,10 +376,6 @@ public void UpdateActiveStyleSheet(BuilderSelection selection, StyleSheet styleS selection.ForceReselection(source); } - // - // Save / Load - // - public bool SaveUnsavedChanges(string manualUxmlPath = null, bool isSaveAs = false) { return SaveNewDocument(null, isSaveAs, out var needsFullRefresh, manualUxmlPath); @@ -410,7 +406,8 @@ public bool SaveNewDocument( } } - List savedUSSFiles = new List(); + var startTime = DateTime.UtcNow; + var savedUSSFiles = new List(); // Save USS files. foreach (var openUSSFile in m_OpenUSSFiles) @@ -487,6 +484,9 @@ public bool SaveNewDocument( ReloadDocumentToCanvas(documentRootElement); hasUnsavedChanges = false; + + var assetSize = uxmlText?.Length ?? 0; + BuilderAnalyticsUtility.SendSaveEvent(startTime, this, newUxmlPath, assetSize); return true; } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs index 874348f5a..d6b308e05 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Explorer/BuilderExplorer.cs @@ -163,10 +163,10 @@ public void UpdateHierarchyAndSelection(bool hasUnsavedChanges) public virtual void HierarchyChanged(VisualElement element, BuilderHierarchyChangeType changeType) { if (element == null || - changeType.HasFlag(BuilderHierarchyChangeType.ChildrenAdded) || - changeType.HasFlag(BuilderHierarchyChangeType.ChildrenRemoved) || - changeType.HasFlag(BuilderHierarchyChangeType.Attributes) || - changeType.HasFlag(BuilderHierarchyChangeType.ClassList)) + (changeType & (BuilderHierarchyChangeType.ChildrenAdded | + BuilderHierarchyChangeType.ChildrenRemoved | + BuilderHierarchyChangeType.Attributes | + BuilderHierarchyChangeType.ClassList)) != 0) { UpdateHierarchyAndSelection(m_Selection.hasUnsavedChanges); m_ShouldRebuildHierarchyOnStyleChange = !m_Selection.hasUnsavedChanges; diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorAttributes.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorAttributes.cs index 5cbb4ad1c..641b21603 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorAttributes.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorAttributes.cs @@ -182,6 +182,11 @@ BuilderStyleRow CreateAttributeRow(VisualElement parent, UxmlAttributeDescriptio uiField.AddToClassList(BuilderConstants.InspectorMultiLineTextFieldClassName); } + if (attribute.name.Equals("mask-character")) + { + uiField.maxLength = 1; + } + fieldElement = uiField; } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorInheritedStyles.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorInheritedStyles.cs index fdea2385b..dad390900 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorInheritedStyles.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorInheritedStyles.cs @@ -268,6 +268,11 @@ void RefreshClassListContainer() var documentRootElement = builderWindow.documentRootElement; + var disabledPills = m_Selection.selectionType == BuilderSelectionType.ElementInTemplateInstance || + m_Selection.selectionType == BuilderSelectionType.ElementInControlInstance || + m_Selection.selectionType == BuilderSelectionType.ParentStyleSelector || + m_Selection.selectionType == BuilderSelectionType.ElementInParentDocument; + foreach (var className in currentVisualElement.GetClasses()) { m_ClassPillTemplate.CloneTree(m_ClassListContainer.contentContainer); @@ -299,11 +304,11 @@ void RefreshClassListContainer() if (selector == null) { pill.AddToClassList(BuilderConstants.InspectorClassPillNotInDocumentClassName); - pill.tooltip = BuilderConstants.InspectorClassPillDoubleClickToCreate; + pill.tooltip = disabledPills ? string.Empty : BuilderConstants.InspectorClassPillDoubleClickToCreate; } else { - pill.tooltip = BuilderConstants.InspectorClassPillDoubleClickToSelect; + pill.tooltip = disabledPills ? string.Empty : BuilderConstants.InspectorClassPillDoubleClickToSelect; } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryContent.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryContent.cs index c1f098c09..5193c127e 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryContent.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryContent.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; @@ -73,6 +72,9 @@ public void OnPostProcessAsset() public static List projectContentTree { get; private set; } public static List projectContentTreeNoPackages { get; private set; } + static HashSet s_StandardEditorControls = new(); + static HashSet s_StandardControls = new(); + static readonly int k_DefaultVisualElementFlexGrow = 1; static readonly Color k_DefaultVisualElementBackgroundColor = new (0, 0, 0, 0); @@ -91,18 +93,28 @@ static BuilderLibraryContent() })); } + public static bool IsEditorOnlyControl(string fullTypeName) + { + return s_StandardEditorControls.Contains(fullTypeName); + } + + public static bool IsStandardControl(string fullTypeName) + { + return s_StandardControls.Contains(fullTypeName); + } + public static void RegenerateLibraryContent() { standardControlsTree = GenerateControlsItemsTree(); standardControlsTreeNoEditor = new List(); + foreach (var item in standardControlsTree) { var builderLibraryTreeItem = item.data; - if (builderLibraryTreeItem.isEditorOnly) - continue; - - standardControlsTreeNoEditor.Add(item); - RemoveEditorOnlyControls(item); + if (!builderLibraryTreeItem.isEditorOnly) + { + standardControlsTreeNoEditor.Add(item); + } } GenerateProjectContentTrees(); @@ -113,22 +125,6 @@ public static void RegenerateLibraryContent() OnLibraryContentUpdated?.Invoke(); } - static void RemoveEditorOnlyControls(TreeViewItem item) - { - var children = new List(item.children); - (item.children as IList)?.Clear(); - foreach (var child in children) - { - var builderLibraryTreeItem = child.data; - if (!builderLibraryTreeItem.isEditorOnly) - { - item.AddChild(child); - if (child.hasChildren) - RemoveEditorOnlyControls(child); - } - } - } - internal static void ResetProjectUxmlPathsHash() { s_ProjectUxmlPathsHash = default; @@ -237,6 +233,9 @@ internal static TreeViewItemData CreateItem(string name, static List GenerateControlsItemsTree() { + s_StandardEditorControls.Clear(); + s_StandardControls.Clear(); + var containersItem = CreateItem(BuilderConstants.LibraryContainersSectionHeaderName, null, null, null, id: 1, isHeader:true); var controlsTree = new List(); IList containersItemList = new List @@ -360,6 +359,19 @@ static List GenerateControlsItemsTree() controlsTree.Add(toolbar); controlsTree.Add(inspectors); + foreach (var categoryData in controlsTree) + { + foreach (var itemData in categoryData.children) + { + if (categoryData.data.isEditorOnly) + { + s_StandardEditorControls.Add(itemData.data.type.FullName); + } + + s_StandardControls.Add(itemData.data.type.FullName); + } + } + return controlsTree; } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs index d60ecc471..03c84c045 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs @@ -291,13 +291,11 @@ public void ImportUxmlFromProject(BuilderLibraryItem projectCategory, bool inclu var tree = vta.CloneTree(); tree.SetProperty(BuilderConstants.LibraryItemLinkedTemplateContainerPathVEPropertyName, assetPath); - tree.name = vta.name; return tree; }, (inVta, inParent, ve) => { var vea = inVta.AddTemplateInstance(inParent, assetPath) as VisualElementAsset; - vea.SetAttribute("name", vta.name); ve.SetProperty(BuilderConstants.ElementLinkedInstancedVisualTreeAssetVEPropertyName, vta); return vea; }, diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAnalyticsUtility.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAnalyticsUtility.cs new file mode 100644 index 000000000..d03590c78 --- /dev/null +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAnalyticsUtility.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEngine.UIElements.StyleSheets; + +namespace Unity.UI.Builder +{ + [Serializable] + class BuilderSaveEventData + { + public string assetGuid; // uxml asset guid + public long assetSize; // uxml asset size, in bytes + public int elements; // number of elements in the Builder Hierarchy + public int depth; // maximum depth of the Builder Hierarchy + public int styleSheets; // number of USS assets referenced by the UXML document + public int selectors; // total number of USS selectors + public int inlineStyles; // number of inline styles on the elements + public int editorElements; // number of Editor-only elements in the Hierarchy + public int customElements; // number of custom elements in the Hierarchy (to the current project) + public bool editorExtension; // whether or not using Editor elements is allowed + public string[] features; // whether or not specific features are used. + } + + [Flags] + enum Features + { + None = 0, + Instances = 1 << 0, + Transitions = 1 << 1, + Transforms = 1 << 2, + Imports = 1 << 3, + CustomProperties = 1 << 4, + AttributeOverrides = 1 << 5, + Variables = 1 << 6, + ListView = 1 << 7, + TreeView = 1 << 8 + } + + internal static class BuilderAnalyticsUtility + { + // Used in tests + public static BuilderSaveEventData cachedSaveEventData { get; private set; } + + /// + /// Gets the feature usage in a uxml file + /// + private static string[] GetFeatureUsage(VisualTreeAsset vta) + { + var features = Features.None; + var stylesheets = new List(); + stylesheets.AddRange(vta.stylesheets); + + if (vta.inlineSheet != null) + { + stylesheets.Add(vta.inlineSheet); + } + + foreach (var stylesheet in stylesheets) + { + if (stylesheet.imports?.Length > 0) + features |= Features.Imports; + + foreach (var rule in stylesheet.rules) + { + if (rule.customPropertiesCount > 0) + features |= Features.CustomProperties; + + foreach (var property in rule.properties) + { + StylePropertyUtil.s_NameToId.TryGetValue(property.name, out var id); + if (id.IsTransitionId()) + { + features |= Features.Transitions; + } + else if (id is StylePropertyId.TransformOrigin or StylePropertyId.Translate + or StylePropertyId.Rotate or StylePropertyId.Scale) + { + features |= Features.Transforms; + } + + if (property.IsVariable()) + { + features |= Features.Variables; + } + } + } + } + + if (vta.templateAssets.Count > 0) + { + features |= Features.Instances; + + if (vta.templateAssets.Any(x => x.attributeOverrides.Count > 0)) + { + features |= Features.AttributeOverrides; + } + } + + if (vta.visualElementAssets.Any(x => x.fullTypeName == typeof(ListView).FullName)) + { + features |= Features.ListView; + } + if (vta.visualElementAssets.Any(x => x.fullTypeName == typeof(TreeView).FullName)) + { + features |= Features.TreeView; + } + + return features == 0 ? Array.Empty() : features.ToString().Split(); + } + + /// + /// It gathers and sends the data for the builder save event + /// + public static void SendSaveEvent(DateTime startTime, BuilderDocumentOpenUXML openUxml, string uxmlPath, + int assetSize) + { + try + { + // If we encounter any error while collecting the data we should still be able to save and skip + // the analytics call + var vta = openUxml.visualTreeAsset; + var openUssFiles = openUxml.openUSSFiles; + var openUxmlFiles = openUxml.openUXMLFiles; + var editorExtensionMode = openUxml.fileSettings.editorExtensionMode; + var idToChildren = VisualTreeAssetUtilities.GenerateIdToChildren(vta); + var elementsInfo = VisualTreeAssetUtilities.GetElementsInfo(idToChildren); + var features = GetFeatureUsage(vta); + + var selectorCount = 0; + foreach (var styleSheetFile in openUssFiles) + { + selectorCount += styleSheetFile.GetComplexSelectorsCount(); + } + + var inlineStyleCount = 0; + foreach (var openUxmlFile in openUxmlFiles) + { + if (openUxmlFile.visualTreeAsset.inlineSheet == null) + { + continue; + } + + foreach (var styleRule in openUxmlFile.visualTreeAsset.inlineSheet.rules) + { + inlineStyleCount += styleRule.properties.Length; + } + } + + var guid = AssetDatabase.AssetPathToGUID(uxmlPath); + + var saveEventData = new BuilderSaveEventData + { + assetGuid = guid, + assetSize = assetSize, + elements = elementsInfo.elements, + depth = elementsInfo.hierarchyDepth, + styleSheets = openUssFiles.Count, + selectors = selectorCount, + inlineStyles = inlineStyleCount, + editorElements = elementsInfo.editorElements, + customElements = elementsInfo.customElements, + editorExtension = editorExtensionMode, + features = features + }; + + cachedSaveEventData = saveEventData; + + var now = DateTime.UtcNow; + var duration = now - startTime; + + // This check avoids analytics sent by tests or batch mode + if (InternalEditorUtility.isHumanControllingUs) + { + UsabilityAnalytics.SendEvent("uiBuilderSave", now, duration, true, saveEventData); + } + } + catch (Exception e) + { + // If there's any error we should log it to potentially fix it + Debug.LogError(e); + } + } + } +} diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetModificationProcessor.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetModificationProcessor.cs index 0733d144d..223d298f7 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetModificationProcessor.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetModificationProcessor.cs @@ -75,19 +75,6 @@ static AssetMoveResult OnWillMoveAsset(string sourcePath, string destinationPath static string[] OnWillSaveAssets(string[] paths) { - // On a duplication, this function is called with no paths. Same for Save and Save Project commands. - // However if we want to save assets on Save/Save Project commands, we have no real choice here but - // to also check if we're here because of the Duplicate command. Ideal situation would be to have - // the Duplicate command trigger its own callback and not OnWillSaveAssets. - var evt = Event.current; - if ((evt == null || evt.commandName != EventCommandNames.Duplicate) && - (paths.Length == 0 || !paths.Any(x => x.Contains(".uxml") || x.Contains(".uss")))) - { - var builder = Builder.ActiveWindow; - if (builder != null && builder.document.hasUnsavedChanges) - builder.SaveChanges(); - } - foreach (var modificationProcessor in m_ModificationProcessors) modificationProcessor.OnAssetChange(); diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetUtilities.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetUtilities.cs index 2913ff906..de8085733 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetUtilities.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Utilities/BuilderAssetUtilities.cs @@ -19,18 +19,29 @@ public static string GetResourcesPathForAsset(Object asset) public static string GetResourcesPathForAsset(string assetPath) { - var resourcesFolder = "Resources/"; - if (string.IsNullOrEmpty(assetPath) || !assetPath.Contains(resourcesFolder)) + if (string.IsNullOrWhiteSpace(assetPath)) return null; - var lastResourcesSubstring = assetPath.LastIndexOf(resourcesFolder) + resourcesFolder.Length; + // Start by trying to find a "Resources" folder in the middle of the path. + var resourcesFolder = "/Resources/"; + var lastResourcesFolderIndex = assetPath.LastIndexOf(resourcesFolder, StringComparison.Ordinal); + // Otherwise check if the "Resources" path is at the start. + if (lastResourcesFolderIndex < 0) + { + if (assetPath.StartsWith("Resources/")) + { + lastResourcesFolderIndex = 0; + resourcesFolder = "Resources/"; + } + else return null; + } + + var lastResourcesSubstring = lastResourcesFolderIndex + resourcesFolder.Length; assetPath = assetPath.Substring(lastResourcesSubstring); - var lastExtDot = assetPath.LastIndexOf("."); + var lastExtDot = assetPath.LastIndexOf(".", StringComparison.Ordinal); if (lastExtDot == -1) - { return null; - } assetPath = assetPath.Substring(0, lastExtDot); @@ -406,7 +417,6 @@ public static void AddElementToSelectionInAsset(BuilderDocument document, Visual } else if (ve.GetVisualElementAsset() != null) { - Undo.IncrementCurrentGroup(); Undo.RegisterCompleteObjectUndo( document.visualTreeAsset, BuilderConstants.ChangeSelectionUndoMessage); @@ -444,7 +454,6 @@ public static void RemoveElementFromSelectionInAsset(BuilderDocument document, V } else if (ve.GetVisualElementAsset() != null) { - Undo.IncrementCurrentGroup(); Undo.RegisterCompleteObjectUndo( document.visualTreeAsset, BuilderConstants.ChangeSelectionUndoMessage); @@ -506,7 +515,7 @@ public static bool HasAttributeOverrideInRootTemplate(VisualElement visualElemen public static List GetAccumulatedAttributeOverrides(VisualElement visualElement) { VisualElement parent = visualElement.parent; - List attributeOverrideRanges = new (); + List attributeOverrideRanges = new(); while (parent != null) { @@ -527,7 +536,7 @@ public static bool HasAttributeOverrideInRootTemplate(VisualElement visualElemen VisualTreeAsset visualTreeAsset = parent.GetVisualTreeAsset(); if (visualTreeAsset != null) { - attributeOverrideRanges.Add(new CreationContext.AttributeOverrideRange(visualTreeAsset, templateAsset.attributeOverrides)); + attributeOverrideRanges.Add(new CreationContext.AttributeOverrideRange(visualTreeAsset, templateAsset.attributeOverrides)); } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Viewport/BuilderInPlaceTextEditingUtilities.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Viewport/BuilderInPlaceTextEditingUtilities.cs index 475c00984..c59954cc2 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Viewport/BuilderInPlaceTextEditingUtilities.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Viewport/BuilderInPlaceTextEditingUtilities.cs @@ -136,8 +136,7 @@ public static void OpenEditor(VisualElement element, Vector2 pos) textInput.style.whiteSpace = s_EditedTextElement.resolvedStyle.whiteSpace; textInput.style.width = s_EditedTextElement.resolvedStyle.width; textInput.style.height = s_EditedTextElement.resolvedStyle.height; - - textEditor.multiline = true; + textEditor.multiline = s_EditedTextElement.edition.multiline; GetAlignmentFromTextAnchor(textElement.resolvedStyle.unityTextAlign, out var alignItems, out var justifyContent); diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/PercentSlider/PercentSlider.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/PercentSlider/PercentSlider.cs index 65ed7e184..ac663674d 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/PercentSlider/PercentSlider.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/PercentSlider/PercentSlider.cs @@ -1,38 +1,27 @@ using System; -using System.Collections.Generic; -using UnityEditor; -using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; namespace Unity.UI.Builder { - internal class PercentSlider : BaseField + internal class PercentSlider : BaseField, IValueField { static readonly string s_UssPath = BuilderConstants.UtilitiesPath + "/PercentSlider/PercentSlider.uss"; static readonly string s_UxmlPath = BuilderConstants.UtilitiesPath + "/PercentSlider/PercentSlider.uxml"; + + static readonly string k_DraggerFieldUssClassName = "unity-style-field__dragger-field"; static readonly string s_UssClassName = "unity-percent-slider"; static readonly string s_VisualInputName = "unity-visual-input"; static readonly string s_SliderName = "unity-slider"; - static readonly string s_FieldName = "unity-field"; public new class UxmlFactory : UxmlFactory { } public new class UxmlTraits : BaseField.UxmlTraits { - public UxmlTraits() - { - } - - public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) - { - base.Init(ve, bag, cc); - } } - SliderInt mSlider; - IntegerField mField; + SliderInt m_Slider; public override void SetValueWithoutNotify(float newValue) { @@ -53,11 +42,14 @@ public PercentSlider(string label) : base(label) template.CloneTree(this); visualInput = this.Q(s_VisualInputName); - mSlider = this.Q(s_SliderName); - mField = this.Q(s_FieldName); + m_Slider = this.Q(s_SliderName); + m_Slider.AddToClassList(k_DraggerFieldUssClassName); - mSlider.RegisterValueChangedCallback(OnSubFieldValueChange); - mField.RegisterValueChangedCallback(OnSubFieldValueChange); + m_Slider.RegisterValueChangedCallback(OnSubFieldValueChange); + + var mouseDragger = new FieldMouseDragger(this); + mouseDragger.SetDragZone(labelElement); + labelElement.AddToClassList(labelDraggerVariantUssClassName); } void RefreshSubFields() @@ -65,11 +57,9 @@ void RefreshSubFields() var value100 = value * 100.0f; var intNewValue = (int)Math.Round(value100, 0); - mSlider.SetValueWithoutNotify(intNewValue); - if (mSlider.elementPanel != null) - mSlider.OnViewDataReady(); // Hack to force update the slide handle position. - - mField.SetValueWithoutNotify(intNewValue); + m_Slider.SetValueWithoutNotify(intNewValue); + if (m_Slider.elementPanel != null) + m_Slider.OnViewDataReady(); // Hack to force update the slide handle position. } void OnSubFieldValueChange(ChangeEvent evt) @@ -82,5 +72,23 @@ void OnSubFieldValueChange(ChangeEvent evt) else value = newValueFloat; } + + void IValueField.ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, float startValue) + { + double sensitivity = NumericFieldDraggerUtility.CalculateIntDragSensitivity((int)startValue, m_Slider.lowValue, m_Slider.highValue); + float acceleration = NumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow); + long v = m_Slider.value; + + v += (long)Math.Round(NumericFieldDraggerUtility.NiceDelta(delta, acceleration) * sensitivity); + m_Slider.value = (int)v; + } + + void IValueField.StartDragging() + { + } + + void IValueField.StopDragging() + { + } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetUtilities.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetUtilities.cs index 0b29eac09..cd089eebd 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetUtilities.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Utilities/VisualTreeAssetExtensions/VisualTreeAssetUtilities.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.UIElements; @@ -7,6 +8,14 @@ namespace Unity.UI.Builder { internal static class VisualTreeAssetUtilities { + public struct ElementsInfo + { + public int elements { get; set; } + public int editorElements { get; set; } + public int customElements { get; set; } + public int hierarchyDepth { get; set; } + } + public static VisualTreeAsset CreateInstance() { var vta = ScriptableObject.CreateInstance(); @@ -202,5 +211,65 @@ public static VisualElementAsset ReparentElementInDocument( return vea; } + + static void GetElementsInfoRecursively(List rootAssets, + Dictionary> idToChildren, ref ElementsInfo elementsInfo) + { + if (rootAssets == null || rootAssets.Count == 0) + { + return; + } + + elementsInfo.hierarchyDepth++; + + rootAssets.Sort(CompareForOrder); + foreach (var rootElement in rootAssets) + { + Assert.IsNotNull(rootElement); + + // Editor elements + if (rootElement.fullTypeName.Contains("UnityEditor") + || BuilderLibraryContent.IsEditorOnlyControl(rootElement.fullTypeName)) + { + elementsInfo.editorElements++; + } + // Custom controls + // i.e. everything that is not a template and is not in the Standard library tab + else if (rootElement.fullTypeName != BuilderConstants.UxmlInstanceTypeName + && !BuilderLibraryContent.IsStandardControl(rootElement.fullTypeName)) + { + elementsInfo.customElements++; + } + + // All elements + elementsInfo.elements++; + + idToChildren.TryGetValue(rootElement.id, out var rootChildAssets); + rootChildAssets?.Sort(CompareForOrder); + GetElementsInfoRecursively(rootChildAssets, idToChildren, ref elementsInfo); + } + } + + /// + /// It gathers the data needed to fill + /// + internal static ElementsInfo GetElementsInfo(Dictionary> idToChildren) + { + ElementsInfo elementsInfo = new ElementsInfo(); + + // Tree root have a parentId == 0 + idToChildren.TryGetValue(0, out var rootAssets); + if (rootAssets == null || rootAssets.Count == 0) + return elementsInfo; + + var root = rootAssets[0]; + rootAssets.Clear(); + idToChildren.TryGetValue(root.id, out rootAssets); + rootAssets?.Sort(CompareForOrder); + + GetElementsInfoRecursively(rootAssets, idToChildren, ref elementsInfo); + + return elementsInfo; + } } } diff --git a/ModuleOverrides/com.unity.ui/Core/ClampedDragger.cs b/ModuleOverrides/com.unity.ui/Core/ClampedDragger.cs index a0086599b..402f87f3b 100644 --- a/ModuleOverrides/com.unity.ui/Core/ClampedDragger.cs +++ b/ModuleOverrides/com.unity.ui/Core/ClampedDragger.cs @@ -44,6 +44,9 @@ protected override void ProcessDownEvent(EventBase evt, Vector2 localPosition, i startMousePosition = localPosition; dragDirection = DragDirection.None; base.ProcessDownEvent(evt, localPosition, pointerId); + + // First click should behave like dragging immediately & ensure that the cursor is clamped on first touch + dragging?.Invoke(); } protected override void ProcessMoveEvent(EventBase evt, Vector2 localPosition) diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseBoolField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseBoolField.cs index 3992330ea..bb2b13438 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseBoolField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseBoolField.cs @@ -2,6 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; + namespace UnityEngine.UIElements { /// @@ -9,6 +11,8 @@ namespace UnityEngine.UIElements /// public abstract class BaseBoolField : BaseField { + internal static readonly DataBindingProperty textProperty = nameof(text); + protected Label m_Label; protected readonly VisualElement m_CheckMark; @@ -52,11 +56,15 @@ private void OnNavigationSubmit(NavigationSubmitEvent evt) /// /// Unity creates a automatically if one does not exist. /// + [CreateProperty] public string text { get { return m_Label?.text; } set { + if (string.CompareOrdinal(m_Label?.text, value) == 0) + return; + if (!string.IsNullOrEmpty(value)) { // Lazy allocation of label if needed... @@ -72,6 +80,7 @@ public string text m_Label.RemoveFromHierarchy(); m_Label = null; } + NotifyPropertyChanged(textProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs index cccb02068..bd15cce3e 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -33,6 +34,12 @@ public enum ListViewReorderMode /// public abstract class BaseListView : BaseVerticalCollectionView { + internal static readonly DataBindingProperty showBoundCollectionSizeProperty = nameof(showBoundCollectionSize); + internal static readonly DataBindingProperty showFoldoutHeaderProperty = nameof(showFoldoutHeader); + internal static readonly DataBindingProperty headerTitleProperty = nameof(headerTitle); + internal static readonly DataBindingProperty showAddRemoveFooterProperty = nameof(showAddRemoveFooter); + internal static readonly DataBindingProperty reorderModeProperty = nameof(reorderMode); + /// /// Defines for the . /// @@ -89,6 +96,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// linked correctly. In production, the collection size rarely displays as a line item in a ListView. /// > /// + [CreateProperty] public bool showBoundCollectionSize { get => m_ShowBoundCollectionSize; @@ -100,6 +108,7 @@ public bool showBoundCollectionSize m_ShowBoundCollectionSize = value; SetupArraySizeField(); + NotifyPropertyChanged(showBoundCollectionSizeProperty); } } @@ -118,6 +127,7 @@ public bool showBoundCollectionSize /// If is set to true, the header includes a TextField to control /// the array size, instead of using the field as part of the list. /// > + [CreateProperty] public bool showFoldoutHeader { get => m_ShowFoldoutHeader; @@ -130,30 +140,37 @@ public bool showFoldoutHeader EnableInClassList(listViewWithHeaderUssClassName, value); - if (m_ShowFoldoutHeader) - { - if (m_Foldout != null) - return; - - m_Foldout = new Foldout() { name = foldoutHeaderUssClassName, text = m_HeaderTitle }; - m_Foldout.AddToClassList(foldoutHeaderUssClassName); - m_Foldout.tabIndex = 1; - hierarchy.Add(m_Foldout); - m_Foldout.Add(scrollView); - } - else if (m_Foldout != null) + try { - m_Foldout?.RemoveFromHierarchy(); - m_Foldout = null; - hierarchy.Add(scrollView); + if (m_ShowFoldoutHeader) + { + if (m_Foldout != null) + return; + + m_Foldout = new Foldout() {name = foldoutHeaderUssClassName, text = m_HeaderTitle}; + m_Foldout.AddToClassList(foldoutHeaderUssClassName); + m_Foldout.tabIndex = 1; + hierarchy.Add(m_Foldout); + m_Foldout.Add(scrollView); + } + else if (m_Foldout != null) + { + m_Foldout?.RemoveFromHierarchy(); + m_Foldout = null; + hierarchy.Add(scrollView); + } + + SetupArraySizeField(); + UpdateListViewLabel(); + + if (showAddRemoveFooter) + { + EnableFooter(true); + } } - - SetupArraySizeField(); - UpdateListViewLabel(); - - if (showAddRemoveFooter) + finally { - EnableFooter(true); + NotifyPropertyChanged(showFoldoutHeaderProperty); } } } @@ -182,15 +199,21 @@ void SetupArraySizeField() /// /// This property controls the text of the foldout header when using . /// + [CreateProperty] public string headerTitle { get => m_HeaderTitle; set { + var previous = m_HeaderTitle; + m_HeaderTitle = value; if (m_Foldout != null) m_Foldout.text = m_HeaderTitle; + + if (string.CompareOrdinal(previous, m_HeaderTitle) != 0) + NotifyPropertyChanged(headerTitleProperty); } } @@ -204,10 +227,18 @@ public string headerTitle /// A "+" button. When clicked, it adds a single item at the end of the list view. /// A "-" button. When clicked, it removes all selected items, or the last item if none are selected. /// + [CreateProperty] public bool showAddRemoveFooter { get => m_Footer != null; - set => EnableFooter(value); + set + { + var previous = showAddRemoveFooter; + EnableFooter(value); + + if (previous != showFoldoutHeader) + NotifyPropertyChanged(showAddRemoveFooterProperty); + } } internal Foldout headerFoldout => m_Foldout; @@ -479,6 +510,7 @@ void OnItemsSourceSizeChanged() /// drop manipulation pushes items with an animation when the reordering happens. /// Multiple item reordering is only supported with the Simple drag mode. /// + [CreateProperty] public ListViewReorderMode reorderMode { get => m_ReorderMode; @@ -490,6 +522,7 @@ public ListViewReorderMode reorderMode InitializeDragAndDropController(reorderable); reorderModeChanged?.Invoke(); Rebuild(); + NotifyPropertyChanged(reorderModeProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BasePopupField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BasePopupField.cs index fccab7f7b..111af3c77 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BasePopupField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BasePopupField.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -using UnityEngine; +using Unity.Properties; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.UIElements @@ -19,6 +19,9 @@ namespace UnityEngine.UIElements [MovedFrom(true, UpgradeConstants.EditorNamespace, UpgradeConstants.EditorAssembly)] public abstract class BasePopupField : BaseField { + internal static readonly DataBindingProperty choicesProperty = nameof(choices); + internal static readonly DataBindingProperty textProperty = nameof(text); + internal List m_Choices; TextElement m_TextElement; VisualElement m_ArrowElement; @@ -48,6 +51,7 @@ protected TextElement textElement /// /// The list of choices to display in the popup menu. /// + [CreateProperty] public virtual List choices { get { return m_Choices; } @@ -60,6 +64,7 @@ public virtual List choices // Make sure to update the text displayed SetValueWithoutNotify(rawValue); + NotifyPropertyChanged(choicesProperty); } } @@ -76,6 +81,7 @@ public override void SetValueWithoutNotify(TValueType newValue) /// /// This is the text displayed to the user for the current selection of the popup. /// + [CreateProperty(ReadOnly = true)] public string text { get { return m_TextElement.text; } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs index 05871f49b..41d9685ef 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseSlider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -29,13 +30,21 @@ public enum SliderDirection public abstract class BaseSlider : BaseField, IValueField where TValueType : System.IComparable { + internal static readonly DataBindingProperty lowValueProperty = nameof(lowValue); + internal static readonly DataBindingProperty highValueProperty = nameof(highValue); + internal static readonly DataBindingProperty rangeProperty = nameof(range); + internal static readonly DataBindingProperty pageSizeProperty = nameof(pageSize); + internal static readonly DataBindingProperty showInputFieldProperty = nameof(showInputField); + internal static readonly DataBindingProperty directionProperty = nameof(direction); + internal static readonly DataBindingProperty invertedProperty = nameof(inverted); + internal VisualElement dragContainer { get; private set; } internal VisualElement dragElement { get; private set; } internal VisualElement trackElement { get; private set; } internal VisualElement dragBorderElement { get; private set; } internal TextField inputTextField { get; private set; } - [SerializeField] + [SerializeField, DontCreateProperty] private TValueType m_LowValue; /// @@ -59,6 +68,7 @@ public UxmlTraits() /// /// This is the minimum value that the slider encodes. /// + [CreateProperty] public TValueType lowValue { get { return m_LowValue; } @@ -70,16 +80,18 @@ public TValueType lowValue ClampValue(); UpdateDragElementPosition(); SaveViewData(); + NotifyPropertyChanged(lowValueProperty); } } } - [SerializeField] + [SerializeField, DontCreateProperty] private TValueType m_HighValue; /// /// This is the maximum value that the slider encodes. /// + [CreateProperty] public TValueType highValue { get { return m_HighValue; } @@ -91,6 +103,7 @@ public TValueType highValue ClampValue(); UpdateDragElementPosition(); SaveViewData(); + NotifyPropertyChanged(highValueProperty); } } } @@ -107,6 +120,7 @@ internal void SetHighValueWithoutNotify(TValueType newHighValue) /// /// This is the range from the minimum value to the maximum value of the slider. /// + [CreateProperty(ReadOnly = true)] public TValueType range { get { return SliderRange(); } @@ -117,10 +131,17 @@ public TValueType range /// /// This is a generic page size used to change the value when clicking in the slider. /// + [CreateProperty] public virtual float pageSize { get { return m_PageSize; } - set { m_PageSize = value; } + set + { + if (m_PageSize == value) + return; + m_PageSize = value; + NotifyPropertyChanged(pageSizeProperty); + } } private bool m_ShowInputField = false; @@ -132,6 +153,7 @@ public virtual float pageSize /// Set this property to true to display a numerical text field that provides another way to /// edit the slider value. /// + [CreateProperty] public virtual bool showInputField { get { return m_ShowInputField; } @@ -141,6 +163,7 @@ public virtual bool showInputField { m_ShowInputField = value; UpdateTextFieldVisibility(); + NotifyPropertyChanged(showInputFieldProperty); } } } @@ -224,11 +247,14 @@ public override void SetValueWithoutNotify(TValueType newValue) /// /// This is the actual property to contain the direction of the slider. /// + [CreateProperty] public SliderDirection direction { get { return m_Direction; } set { + var previous = m_Direction; + m_Direction = value; if (m_Direction == SliderDirection.Horizontal) { @@ -240,6 +266,8 @@ public SliderDirection direction RemoveFromClassList(horizontalVariantUssClassName); AddToClassList(verticalVariantUssClassName); } + if (previous != m_Direction) + NotifyPropertyChanged(directionProperty); } } @@ -250,6 +278,7 @@ public SliderDirection direction /// For an inverted horizontal slider, high value is located to the left, low value is located to the right /// For an inverted vertical slider, high value is located to the bottom, low value is located to the top. /// + [CreateProperty] public bool inverted { get { return m_Inverted; } @@ -259,6 +288,7 @@ public bool inverted { m_Inverted = value; UpdateDragElementPosition(); + NotifyPropertyChanged(invertedProperty); } } } @@ -690,6 +720,7 @@ private void UpdateTextFieldVisibility() { inputTextField = new TextField() { name = "unity-text-field" }; inputTextField.AddToClassList(textFieldClassName); + inputTextField.RegisterCallback(OnInputNavigationMoveEvent, TrickleDown.TrickleDown); inputTextField.RegisterValueChangedCallback(OnTextFieldValueChange); inputTextField.RegisterCallback(OnTextFieldFocusOut); visualInput.Add(inputTextField); @@ -701,6 +732,7 @@ private void UpdateTextFieldVisibility() if (inputTextField.panel != null) inputTextField.RemoveFromHierarchy(); + inputTextField.UnregisterCallback(OnInputNavigationMoveEvent); inputTextField.UnregisterValueChangedCallback(OnTextFieldValueChange); inputTextField.UnregisterCallback(OnTextFieldFocusOut); inputTextField = null; @@ -720,6 +752,12 @@ private void OnTextFieldFocusOut(FocusOutEvent evt) UpdateTextFieldValue(); } + private void OnInputNavigationMoveEvent(NavigationMoveEvent evt) + { + // The input field should not do any navigation when using the arrow keys. + evt.StopPropagation(); + } + void OnTextFieldValueChange(ChangeEvent evt) { var newValue = GetClampedValue(ParseStringToValue(evt.newValue)); @@ -741,7 +779,7 @@ protected override void UpdateMixedValueContent() } else { - visualInput.Add(dragElement); + dragContainer.Add(dragElement); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseTreeView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseTreeView.cs index 3e32a1626..46cf41a4d 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseTreeView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseTreeView.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -14,6 +15,8 @@ namespace UnityEngine.UIElements /// public abstract class BaseTreeView : BaseVerticalCollectionView { + internal static readonly DataBindingProperty autoExpandProperty = nameof(autoExpand); + /// /// The USS class name for TreeView elements. /// @@ -92,9 +95,10 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// To set the items source, use instead, which allows fully typed items. /// + [CreateProperty(ReadOnly = true)] public new IList itemsSource { - get => viewController.itemsSource; + get => viewController?.itemsSource; internal set => GetOrCreateViewController().itemsSource = value; } @@ -174,18 +178,23 @@ void OnItemIndexChanged(int srcIndex, int dstIndex) /// /// When true, items are automatically expanded when added to the TreeView. /// + [CreateProperty] public bool autoExpand { get => m_AutoExpand; set { + if (m_AutoExpand == value) + return; + m_AutoExpand = value; viewController?.RegenerateWrappers(); RefreshItems(); + NotifyPropertyChanged(autoExpandProperty); } } - [SerializeField] + [SerializeField, DontCreateProperty] private List m_ExpandedItemIds; internal List expandedItemIds diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs index 635da8c1b..8d12181e3 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Profiling; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -61,6 +62,19 @@ class SerializedVirtualizationData /// public abstract class BaseVerticalCollectionView : BindableElement, ISerializationCallbackReceiver { + internal static readonly DataBindingProperty itemsSourceProperty = nameof(itemsSource); + internal static readonly DataBindingProperty selectionTypeProperty = nameof(selectionType); + internal static readonly DataBindingProperty selectedItemProperty = nameof(selectedItem); + internal static readonly DataBindingProperty selectedItemsProperty = nameof(selectedItems); + internal static readonly DataBindingProperty selectedIndexProperty = nameof(selectedIndex); + internal static readonly DataBindingProperty selectedIndicesProperty = nameof(selectedIndices); + internal static readonly DataBindingProperty showBorderProperty = nameof(showBorder); + internal static readonly DataBindingProperty reorderableProperty = nameof(reorderable); + internal static readonly DataBindingProperty horizontalScrollingEnabledProperty = nameof(horizontalScrollingEnabled); + internal static readonly DataBindingProperty showAlternatingRowBackgroundsProperty = nameof(showAlternatingRowBackgrounds); + internal static readonly DataBindingProperty virtualizationMethodProperty = nameof(virtualizationMethod); + internal static readonly DataBindingProperty fixedItemHeightProperty = nameof(fixedItemHeight); + /// /// Defines for the . /// @@ -218,10 +232,17 @@ internal Func getItemId /// /// This list contains the items that the displays. /// + [CreateProperty] public IList itemsSource { get => viewController?.itemsSource; - set => GetOrCreateViewController().itemsSource = value; + set + { + var previous = itemsSource; + GetOrCreateViewController().itemsSource = value; + if (previous != itemsSource) + NotifyPropertyChanged(itemsSourceProperty); + } } internal virtual bool sourceIncludesArraySize => false; @@ -281,11 +302,13 @@ public Action destroyItem /// The default value is . /// When you set the collection view to disable selections, any current selection is cleared. /// + [CreateProperty] public SelectionType selectionType { get { return m_SelectionType; } set { + var previous = m_SelectionType; m_SelectionType = value; if (m_SelectionType == SelectionType.None) { @@ -298,34 +321,47 @@ public SelectionType selectionType SetSelection(m_SelectedIndices.First()); } } + + if (previous != m_SelectionType) + NotifyPropertyChanged(selectionTypeProperty); } } /// /// Returns the selected item from the data source. If multiple items are selected, returns the first selected item. /// + [CreateProperty(ReadOnly = true)] public object selectedItem => m_SelectedItems.Count == 0 ? null : m_SelectedItems.First(); /// /// Returns the selected items from the data source. Always returns an enumerable, even if no item is selected, or a single /// item is selected. /// + [CreateProperty(ReadOnly = true)] public IEnumerable selectedItems => m_SelectedItems; /// /// Returns or sets the selected item's index in the data source. If multiple items are selected, returns the /// first selected item's index. If multiple items are provided, sets them all as selected. /// + [CreateProperty] public int selectedIndex { get { return m_SelectedIndices.Count == 0 ? -1 : m_SelectedIndices.First(); } - set { SetSelection(value); } + set + { + var previous = selectedIndex; + SetSelection(value); + if (previous != selectedIndex) + NotifyPropertyChanged(selectedIndexProperty); + } } /// /// Returns the indices of selected items in the data source. Always returns an enumerable, even if no item is selected, or a /// single item is selected. /// + [CreateProperty(ReadOnly = true)] public IEnumerable selectedIndices => m_SelectedIndices; internal List currentSelectionIds => m_SelectedIds; @@ -364,10 +400,18 @@ internal float ResolveItemHeight(float height = -1) /// /// If set to true, a border appears around the ScrollView that the collection view uses internally. /// + [CreateProperty] public bool showBorder { get => m_ScrollView.ClassListContains(borderUssClassName); - set => m_ScrollView.EnableInClassList(borderUssClassName, value); + set + { + var previous = showBorder; + m_ScrollView.EnableInClassList(borderUssClassName, value); + + if (previous != showBorder) + NotifyPropertyChanged(showBorderProperty); + } } /// @@ -379,26 +423,37 @@ public bool showBorder /// provides a default controller to allow standard behavior. It also automatically handles reordering /// the items in the data source. /// + [CreateProperty] public bool reorderable { get => m_Dragger?.dragAndDropController?.enableReordering ?? false; set { - if (m_Dragger?.dragAndDropController == null) + var previous = reorderable; + + try { - if (value) + if (m_Dragger?.dragAndDropController == null) { - InitializeDragAndDropController(true); + if (value) + { + InitializeDragAndDropController(true); + } + + return; } - return; + var controller = m_Dragger.dragAndDropController; + if (controller != null && controller.enableReordering != value) + { + controller.enableReordering = value; + Rebuild(); + } } - - var controller = m_Dragger.dragAndDropController; - if (controller != null && controller.enableReordering != value) + finally { - controller.enableReordering = value; - Rebuild(); + if (previous != reorderable) + NotifyPropertyChanged(reorderableProperty); } } } @@ -409,6 +464,7 @@ public bool reorderable /// This property controls whether the collection view shows a horizontal scroll bar when its content /// does not fit in the visible area. /// + [CreateProperty] public bool horizontalScrollingEnabled { get { return m_HorizontalScrollingEnabled; } @@ -419,16 +475,18 @@ public bool horizontalScrollingEnabled m_HorizontalScrollingEnabled = value; m_ScrollView.mode = (value ? ScrollViewMode.VerticalAndHorizontal : ScrollViewMode.Vertical); + NotifyPropertyChanged(horizontalScrollingEnabledProperty); } } - [SerializeField] + [SerializeField, DontCreateProperty] private AlternatingRowBackground m_ShowAlternatingRowBackgrounds = AlternatingRowBackground.None; /// /// This property controls whether the background colors of collection view rows alternate. /// Takes a value from the enum. /// + [CreateProperty] public AlternatingRowBackground showAlternatingRowBackgrounds { get { return m_ShowAlternatingRowBackgrounds; } @@ -439,9 +497,11 @@ public AlternatingRowBackground showAlternatingRowBackgrounds m_ShowAlternatingRowBackgrounds = value; RefreshItems(); + NotifyPropertyChanged(showAlternatingRowBackgroundsProperty); } } + internal static readonly string k_InvalidTemplateError = "Template Not Found"; // If we ever change the default item height, we should consider changing the default max height of the view when // used in property fields. The rule to look for is ".unity-property-field > .unity-collection-view" internal static readonly int s_DefaultItemHeight = 22; @@ -460,18 +520,18 @@ public AlternatingRowBackground showAlternatingRowBackgrounds /// When using DynamicHeight, the collection will wait for the actual height to be computed. /// Dynamic height is more flexible but less performant. /// + [CreateProperty] public CollectionVirtualizationMethod virtualizationMethod { get => m_VirtualizationMethod; set { - var oldValue = m_VirtualizationMethod; + if (m_VirtualizationMethod == value) + return; m_VirtualizationMethod = value; - if (oldValue != value) - { - CreateVirtualizationController(); - Rebuild(); - } + CreateVirtualizationController(); + Rebuild(); + NotifyPropertyChanged(virtualizationMethodProperty); } } @@ -494,11 +554,14 @@ public int itemHeight /// /// This property must be set when using the is set to FixedHeight, for the collection view to function. /// + [CreateProperty] public float fixedItemHeight { get => m_FixedItemHeight; set { + var previous = fixedItemHeight; + if (value < 0) throw new ArgumentOutOfRangeException(nameof(fixedItemHeight), "Value needs to be positive for virtualization."); @@ -507,6 +570,7 @@ public float fixedItemHeight { m_FixedItemHeight = value; RefreshItems(); + NotifyPropertyChanged(fixedItemHeightProperty); } } } @@ -516,11 +580,11 @@ public float fixedItemHeight CollectionVirtualizationController m_VirtualizationController; KeyboardNavigationManipulator m_NavigationManipulator; - [SerializeField] + [SerializeField, DontCreateProperty] internal SerializedVirtualizationData serializedVirtualizationData = new SerializedVirtualizationData(); // Persisted. It's why this can't be a HashSet(). :( - [SerializeField] + [SerializeField, DontCreateProperty] private readonly List m_SelectedIds = new List(); // Not persisted! Just used for fast lookups of selected indices and object references. diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/DropdownField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/DropdownField.cs index 84fa9ded3..7acf21a08 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/DropdownField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/DropdownField.cs @@ -30,7 +30,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext base.Init(ve, bag, cc); var f = (DropdownField)ve; - var choices = ParseChoiceList(m_Choices.GetValueFromBag(bag, cc)); + var choices = UxmlUtility.ParseStringListAttribute(m_Choices.GetValueFromBag(bag, cc)); if (choices != null) f.choices = choices; f.index = m_Index.GetValueFromBag(bag, cc); diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/EnumField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/EnumField.cs index 7c980a727..3a2435eb7 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/EnumField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/EnumField.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; using UnityEngine; using UnityEngine.Scripting.APIUpdating; @@ -46,6 +47,8 @@ internal static bool ExtractValue(IUxmlAttributes bag, CreationContext cc, out T [MovedFrom(true, UpgradeConstants.EditorNamespace, UpgradeConstants.EditorAssembly)] public class EnumField : BaseField { + internal static readonly DataBindingProperty textProperty = nameof(text); + /// /// Instantiates an using the data read from a UXML file. /// @@ -112,6 +115,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// Return the text value of the currently selected enum. /// + [CreateProperty(ReadOnly = true)] public string text { get { return m_TextElement.text; } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/Foldout.cs b/ModuleOverrides/com.unity.ui/Core/Controls/Foldout.cs index e4e0febe4..048bde6ec 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/Foldout.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/Foldout.cs @@ -2,6 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; + namespace UnityEngine.UIElements { /// @@ -15,6 +17,9 @@ namespace UnityEngine.UIElements /// public class Foldout : BindableElement, INotifyValueChanged { + internal static readonly DataBindingProperty textProperty = nameof(text); + internal static readonly DataBindingProperty valueProperty = nameof(value); + /// /// Instantiates a using the data from a UXML file. /// @@ -54,10 +59,10 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext } Toggle m_Toggle; - VisualElement m_Container; - internal Toggle toggle => m_Toggle; + VisualElement m_Container; + /// /// This element contains the elements that are shown or hidden when you toggle the . /// @@ -66,23 +71,28 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// This is the text of the toggle's label. /// + [CreateProperty] public string text { get => m_Toggle.text; set { + var previous = text; m_Toggle.text = value; m_Toggle.visualInput.Q(className: Toggle.textUssClassName)?.AddToClassList(textUssClassName); + if (string.CompareOrdinal(previous, text) != 0) + NotifyPropertyChanged(textProperty); } } - [SerializeField] + [SerializeField, DontCreateProperty] private bool m_Value; /// /// This is the state of the Foldout's toggle. It is true if the is open and its contents are /// visible, and false if the Foldout is closed, and its contents are hidden. /// + [CreateProperty] public bool value { get => m_Value; @@ -97,6 +107,7 @@ public bool value SetValueWithoutNotify(value); SendEvent(evt); SaveViewData(); + NotifyPropertyChanged(valueProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/GenericDropdownMenu.cs b/ModuleOverrides/com.unity.ui/Core/Controls/GenericDropdownMenu.cs index 00ec09023..a0810ecdc 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/GenericDropdownMenu.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/GenericDropdownMenu.cs @@ -148,6 +148,7 @@ void Hide(bool giveFocusBack = false) if (m_TargetElement != null) { + m_TargetElement.UnregisterCallback(OnTargetElementDetachFromPanel); m_TargetElement.pseudoStates ^= PseudoStates.Active; if (giveFocusBack) m_TargetElement.Focus(); diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/GroupBox.cs b/ModuleOverrides/com.unity.ui/Core/Controls/GroupBox.cs index 3dd682525..6582a74d2 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/GroupBox.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/GroupBox.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -14,6 +15,8 @@ namespace UnityEngine.UIElements /// public class GroupBox : BindableElement, IGroupBox { + internal static readonly DataBindingProperty textProperty = nameof(text); + /// /// Instantiates a using data from a UXML file. /// @@ -60,11 +63,14 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// The title text of the box. /// + [CreateProperty] public string text { get => m_TitleLabel?.text; set { + var previous = text; + if (!string.IsNullOrEmpty(value)) { // Lazy allocation of label if needed... @@ -82,6 +88,9 @@ public string text m_TitleLabel.RemoveFromHierarchy(); m_TitleLabel = null; } + + if (string.CompareOrdinal(previous, text) != 0) + NotifyPropertyChanged(textProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/HelpBox.cs b/ModuleOverrides/com.unity.ui/Core/Controls/HelpBox.cs index 9c4b36ae6..444a9880f 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/HelpBox.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/HelpBox.cs @@ -2,6 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using Unity.Properties; + namespace UnityEngine.UIElements { /// @@ -50,6 +52,9 @@ public enum HelpBoxMessageType /// public class HelpBox : VisualElement { + internal static readonly DataBindingProperty textProperty = nameof(text); + internal static readonly DataBindingProperty messageTypeProperty = nameof(messageType); + /// /// The USS class name for Elements of this type. /// @@ -112,15 +117,24 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// The message text. /// + [CreateProperty] public string text { get { return m_Label.text; } - set { m_Label.text = value; } + set + { + var previous = text; + m_Label.text = value; + + if (string.CompareOrdinal(previous, text) != 0) + NotifyPropertyChanged(textProperty); + } } /// /// The type of message. /// + [CreateProperty] public HelpBoxMessageType messageType { get { return m_HelpBoxMessageType; } @@ -130,6 +144,7 @@ public HelpBoxMessageType messageType { m_HelpBoxMessageType = value; UpdateIcon(value); + NotifyPropertyChanged(messageTypeProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/Image.cs b/ModuleOverrides/com.unity.ui/Core/Controls/Image.cs index f9e3f4100..34520b540 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/Image.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/Image.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine.UIElements.StyleSheets; namespace UnityEngine.UIElements @@ -13,6 +14,14 @@ namespace UnityEngine.UIElements /// public class Image : VisualElement { + internal static readonly DataBindingProperty imageProperty = nameof(image); + internal static readonly DataBindingProperty spriteProperty = nameof(sprite); + internal static readonly DataBindingProperty vectorImageProperty = nameof(vectorImage); + internal static readonly DataBindingProperty sourceRectProperty = nameof(sourceRect); + internal static readonly DataBindingProperty uvProperty = nameof(uv); + internal static readonly DataBindingProperty scaleModeProperty = nameof(scaleMode); + internal static readonly DataBindingProperty tintColorProperty = nameof(tintColor); + /// /// Instantiates an using the data read from a UXML file. /// @@ -43,56 +52,62 @@ public override IEnumerable uxmlChildElementsDescri private bool m_ScaleModeIsInline; private bool m_TintColorIsInline; - /// /// The texture to display in this image. /// + [CreateProperty] public Texture image { get { return m_Image; } set { + if (m_Image == value) + return; + if (value != null && (m_Sprite != null || m_VectorImage != null)) { var unsetProp = m_Sprite != null ? "sprite" : "vector image"; Debug.LogWarning($"Image object already has a background, removing {unsetProp}"); - m_Sprite = null; - m_VectorImage = null; + sprite = null; + vectorImage = null; } + m_ImageIsInline = value != null; - if (m_Image != value) + m_Image = value; + IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); + if (m_Image == null) { - m_Image = value; - IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); - if (m_Image == null) - { - m_UV = new Rect(0, 0, 1, 1); - } + uv = new Rect(0, 0, 1, 1); } + + NotifyPropertyChanged(imageProperty); } } /// /// The sprite to display in this image. /// + [CreateProperty] public Sprite sprite { get { return m_Sprite; } set { + if (m_Sprite == value) + return; + if (value != null && (m_Image != null || m_VectorImage != null)) { var unsetProp = m_Image != null ? "texture" : "vector image"; Debug.LogWarning($"Image object already has a background, removing {unsetProp}"); - m_Image = null; - m_VectorImage = null; + image = null; + vectorImage = null; } + m_ImageIsInline = value != null; - if (m_Sprite != value) - { - m_Sprite = value; - IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); - } + m_Sprite = value; + IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); + NotifyPropertyChanged(spriteProperty); } } @@ -100,73 +115,94 @@ public Sprite sprite /// /// The to display in this image. /// + [CreateProperty] public VectorImage vectorImage { get { return m_VectorImage; } set { + if (m_VectorImage == value) + return; + if (value != null && (m_Image != null || m_Sprite != null)) { var unsetProp = m_Image != null ? "texture" : "sprite"; Debug.LogWarning($"Image object already has a background, removing {unsetProp}"); - m_Image = null; - m_Sprite = null; + image = null; + sprite = null; } + m_ImageIsInline = value != null; - if (m_VectorImage != value) + m_VectorImage = value; + IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); + if (m_VectorImage == null) { - m_VectorImage = value; - IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); - if (m_VectorImage == null) - { - m_UV = new Rect(0, 0, 1, 1); - } + uv = new Rect(0, 0, 1, 1); } + + NotifyPropertyChanged(vectorImageProperty); } } /// /// The source rectangle inside the texture relative to the top left corner. /// + [CreateProperty] public Rect sourceRect { get { return GetSourceRect(); } set { + if (GetSourceRect() == value) + return; + if (sprite != null) { Debug.LogError("Cannot set sourceRect on a sprite image"); return; } CalculateUV(value); + NotifyPropertyChanged(sourceRectProperty); } } /// /// The base texture coordinates of the Image relative to the bottom left corner. /// + [CreateProperty] public Rect uv { get { return m_UV; } - set { m_UV = value; } + set + { + if (m_UV == value) + return; + m_UV = value; + NotifyPropertyChanged(uvProperty); + } } /// /// ScaleMode used to display the Image. /// + [CreateProperty] public ScaleMode scaleMode { get { return m_ScaleMode; } set { + if (m_ScaleMode == value && m_ScaleModeIsInline) + return; m_ScaleModeIsInline = true; SetScaleMode(value); + NotifyPropertyChanged(scaleModeProperty); } } /// /// Tinting color for this Image. /// + [CreateProperty] public Color tintColor { get @@ -175,12 +211,13 @@ public Color tintColor } set { + if (m_TintColor == value && m_TintColorIsInline) + return; + m_TintColorIsInline = true; - if (m_TintColor != value) - { - m_TintColor = value; - IncrementVersion(VersionChangeType.Repaint); - } + m_TintColor = value; + IncrementVersion(VersionChangeType.Repaint); + NotifyPropertyChanged(tintColorProperty); } } @@ -251,9 +288,10 @@ protected internal override Vector2 DoMeasure(float desiredWidth, MeasureMode wi // covers the MeasureMode.Exactly case Rect rect = sourceRect; - bool hasImagePosition = rect != Rect.zero; - measuredWidth = hasImagePosition ? rect.width : sourceSize.x; - measuredHeight = hasImagePosition ? rect.height : sourceSize.y; + bool hasRect = rect != Rect.zero; + // UUM-17229: rect width/height can be negative (e.g. when the UVs are flipped) + measuredWidth = hasRect ? Mathf.Abs(rect.width) : sourceSize.x; + measuredHeight = hasRect ? Mathf.Abs(rect.height) : sourceSize.y; if (widthMode == MeasureMode.AtMost) { @@ -281,7 +319,7 @@ private void OnGenerateVisualContent(MeshGenerationContext mgc) else if (sprite != null) { var slices = Vector4.zero; - rectParams = UIR.MeshGenerator.RectangleParams.MakeSprite(alignedRect, sprite, scaleMode, panel.contextType, false, ref slices); + rectParams = UIR.MeshGenerator.RectangleParams.MakeSprite(alignedRect, uv, sprite, scaleMode, panel.contextType, false, ref slices); } else if (vectorImage != null) rectParams = UIR.MeshGenerator.RectangleParams.MakeVectorTextured(alignedRect, uv, vectorImage, scaleMode, panel.contextType); diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs index 5d279f3f7..5bf4d55b5 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -17,6 +18,10 @@ internal interface IPrefixLabel /// public abstract class BaseField : BindableElement, INotifyValueChanged, IMixedValueSupport, IPrefixLabel { + internal static readonly DataBindingProperty valueProperty = nameof(value); + internal static readonly DataBindingProperty labelProperty = nameof(label); + internal static readonly DataBindingProperty showMixedValueProperty = nameof(showMixedValue); + /// /// Defines for the . /// @@ -38,26 +43,6 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext base.Init(ve, bag, cc); ((BaseField)ve).label = m_Label.GetValueFromBag(bag, cc); } - - internal static List ParseChoiceList(string choicesFromBag) - { - if (string.IsNullOrEmpty(choicesFromBag.Trim())) - return null; - - // Here the choices is comma separated in the string... - var choices = choicesFromBag.Split(','); - - if (choices.Length != 0) - { - var listOfChoices = new List(); - foreach (var choice in choices) - { - listOfChoices.Add(choice.Trim()); - } - return listOfChoices; - } - return null; - } } /// @@ -138,7 +123,7 @@ internal VisualElement visualInput } } - [SerializeField] + [SerializeField, DontCreateProperty] TValueType m_Value; /// @@ -155,6 +140,7 @@ protected TValueType rawValue /// /// The value associated with the field. /// + [CreateProperty] public virtual TValueType value { get @@ -175,6 +161,7 @@ public virtual TValueType value evt.elementTarget = this; SendEvent(evt); } + NotifyPropertyChanged(valueProperty); } else { @@ -191,6 +178,7 @@ public virtual TValueType value /// /// The string representing the label that will appear beside the field. /// + [CreateProperty] public string label { get @@ -202,7 +190,6 @@ public string label if (labelElement.text != value) { labelElement.text = value; - if (string.IsNullOrEmpty(labelElement.text)) { AddToClassList(noLabelVariantUssClassName); @@ -216,6 +203,8 @@ public string label RemoveFromClassList(noLabelVariantUssClassName); } } + + NotifyPropertyChanged(labelProperty); } } } @@ -225,6 +214,7 @@ public string label /// /// When set to true, gives the field the appearance of editing multiple different values. /// + [CreateProperty] public bool showMixedValue { get => m_ShowMixedValue; @@ -236,6 +226,8 @@ public bool showMixedValue // Once value has been set, update the field's appearance UpdateMixedValueContent(); + + NotifyPropertyChanged(showMixedValueProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/KeyboardTextEditor.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/KeyboardTextEditor.cs index f306392c3..07336a2b6 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/KeyboardTextEditor.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/KeyboardTextEditor.cs @@ -94,11 +94,13 @@ void OnKeyDown(KeyDownEvent evt) return; // We rely on KeyCode.Tab for both navigation and inserting tabulation. - if (c == '\t') + // On Linux Platform regular tab event occupies both keycode and character fields + if (c == '\t' && evt.keyCode == KeyCode.None && evt.modifiers == EventModifiers.None) return; // Ignore tab in single-line text fields, modifier+tab in multiline text fields - if (evt.keyCode == KeyCode.Tab) + // On Linux Platform modifier+tab case will be represented as keycode=None and character='\t' + if (evt.keyCode == KeyCode.Tab || (evt.keyCode == KeyCode.Tab && evt.character == '\t' && evt.modifiers == EventModifiers.Shift)) { if(!textElement.edition.multiline || evt.shiftKey) { @@ -145,7 +147,7 @@ void OnKeyDown(KeyDownEvent evt) if (!textElement.edition.AcceptCharacter(c)) return; - if (c >= k_Space || evt.keyCode == KeyCode.Tab || c == '\n' || c == '\r' || c == k_LineFeed) + if (c >= k_Space || evt.keyCode == KeyCode.Tab || (textElement.edition.multiline && !evt.altKey && (c == '\n' || c == '\r' || c == k_LineFeed))) { editingUtilities.Insert(c); m_Changed = true; diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs index 278a284c3..4858ce088 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -12,6 +13,8 @@ namespace UnityEngine.UIElements /// public class TextField : TextInputBaseField { + internal static readonly DataBindingProperty multilineProperty = nameof(multiline); + // This property to alleviate the fact we have to cast all the time TextInput textInput => (TextInput)textInputBase; @@ -61,10 +64,18 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// Set this to true to allow multiple lines in the textfield and false if otherwise. /// + [CreateProperty] public bool multiline { get { return textInput.multiline; } - set { textInput.multiline = value; } + set + { + var previous = multiline; + textInput.multiline = value; + + if (previous != multiline) + NotifyPropertyChanged(multilineProperty); + } } /// diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextInputFieldBase.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextInputFieldBase.cs index ab10eb8b6..d2c4d8c20 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextInputFieldBase.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextInputFieldBase.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -11,6 +12,27 @@ namespace UnityEngine.UIElements /// public abstract class TextInputBaseField : BaseField { + internal static readonly DataBindingProperty autoCorrectionProperty = nameof(autoCorrection); + internal static readonly DataBindingProperty hideMobileInputProperty = nameof(hideMobileInput); + internal static readonly DataBindingProperty keyboardTypeProperty = nameof(keyboardType); + internal static readonly DataBindingProperty isReadOnlyProperty = nameof(isReadOnly); + internal static readonly DataBindingProperty isPasswordFieldProperty = nameof(isPasswordField); + internal static readonly DataBindingProperty textSelectionProperty = nameof(textSelection); + internal static readonly DataBindingProperty textEditionProperty = nameof(textEdition); + internal static readonly DataBindingProperty selectionColorProperty = nameof(selectionColor); + internal static readonly DataBindingProperty cursorColorProperty = nameof(cursorColor); + internal static readonly DataBindingProperty cursorIndexProperty = nameof(cursorIndex); + internal static readonly DataBindingProperty cursorPositionProperty = nameof(cursorPosition); + internal static readonly DataBindingProperty selectIndexProperty = nameof(selectIndex); + internal static readonly DataBindingProperty selectAllOnFocusProperty = nameof(selectAllOnFocus); + internal static readonly DataBindingProperty selectAllOnMouseUpProperty = nameof(selectAllOnMouseUp); + internal static readonly DataBindingProperty maxLengthProperty = nameof(maxLength); + internal static readonly DataBindingProperty doubleClickSelectsWordProperty = nameof(doubleClickSelectsWord); + internal static readonly DataBindingProperty tripleClickSelectsLineProperty = nameof(tripleClickSelectsLine); + internal static readonly DataBindingProperty isDelayedProperty = nameof(isDelayed); + internal static readonly DataBindingProperty maskCharProperty = nameof(maskChar); + internal static readonly DataBindingProperty verticalScrollerVisibilityProperty = nameof(verticalScrollerVisibility); + static CustomStyleProperty s_SelectionColorProperty = new CustomStyleProperty("--unity-selection-color"); static CustomStyleProperty s_CursorColorProperty = new CustomStyleProperty("--unity-cursor-color"); internal const int kMaxLengthNone = -1; @@ -212,11 +234,13 @@ protected TextInputBaseField(string label, int maxLength, char maskChar, TextInp /// /// Retrieves this Field's TextElement ITextSelection /// + [CreateProperty(ReadOnly = true)] public ITextSelection textSelection => m_TextInputBase.textElement.selection; /// /// Retrieves this Field's TextElement ITextEdition /// + [CreateProperty(ReadOnly = true)] public ITextEdition textEdition => m_TextInputBase.textElement.edition; #region TextEdition @@ -232,16 +256,24 @@ protected Action onIsReadOnlyChanged /// /// Returns true if the field is read only. /// + [CreateProperty] public bool isReadOnly { get => textEdition.isReadOnly; - set => textEdition.isReadOnly = value; + set + { + if (textEdition.isReadOnly == value) + return; + textEdition.isReadOnly = value; + NotifyPropertyChanged(isReadOnlyProperty); + } } // Password field (indirectly lossy behaviour when activated via multiline) /// /// Returns true if the field is used to edit a password. /// + [CreateProperty] public bool isPasswordField { get => textEdition.isPassword; @@ -252,34 +284,56 @@ public bool isPasswordField textEdition.isPassword = value; m_TextInputBase.IncrementVersion(VersionChangeType.Repaint); + NotifyPropertyChanged(isPasswordFieldProperty); } } /// /// Determines if the touch screen keyboard auto correction is turned on or off. /// + [CreateProperty] public bool autoCorrection { get => textEdition.autoCorrection; - set => textEdition.autoCorrection = value; + set + { + if (textEdition.autoCorrection == value) + return; + textEdition.autoCorrection = value; + NotifyPropertyChanged(autoCorrectionProperty); + } } /// /// Hides or shows the mobile input field. /// + [CreateProperty] public bool hideMobileInput { get => textEdition.hideMobileInput; - set => textEdition.hideMobileInput = value; + set + { + if (textEdition.hideMobileInput == value) + return; + textEdition.hideMobileInput = value; + NotifyPropertyChanged(hideMobileInputProperty); + } } /// /// The type of mobile keyboard that will be used. /// + [CreateProperty] public TouchScreenKeyboardType keyboardType { get => textEdition.keyboardType; - set => textEdition.keyboardType = value; + set + { + if (textEdition.keyboardType == value) + return; + textEdition.keyboardType = value; + NotifyPropertyChanged(keyboardTypeProperty); + } } /// @@ -293,28 +347,49 @@ public TouchScreenKeyboard touchScreenKeyboard /// /// Maximum number of characters for the field. /// + [CreateProperty] public int maxLength { get => textEdition.maxLength; - set => textEdition.maxLength = value; + set + { + if (textEdition.maxLength == value) + return; + textEdition.maxLength = value; + NotifyPropertyChanged(maxLengthProperty); + } } /// /// If set to true, the value property isn't updated until either the user presses Enter or the text field loses focus. /// + [CreateProperty] public bool isDelayed { get => textEdition.isDelayed; - set => textEdition.isDelayed = value; + set + { + if (textEdition.isDelayed == value) + return; + textEdition.isDelayed = value; + NotifyPropertyChanged(isDelayedProperty); + } } /// /// The character used for masking in a password field. /// + [CreateProperty] public char maskChar { get => textEdition.maskChar; - set => textEdition.maskChar = value; + set + { + if (textEdition.maskChar == value) + return; + textEdition.maskChar = value; + NotifyPropertyChanged(maskCharProperty); + } } #endregion @@ -323,24 +398,34 @@ public char maskChar /// /// Background color of selected text. /// + [CreateProperty(ReadOnly = true)] public Color selectionColor => textSelection.selectionColor; /// /// Color of the cursor. /// + [CreateProperty(ReadOnly = true)] public Color cursorColor => textSelection.cursorColor; /// /// This is the cursor index in the text presented. /// + [CreateProperty] public int cursorIndex { get => textSelection.cursorIndex; - set => textSelection.cursorIndex = value; + set + { + if (textSelection.cursorIndex == value) + return; + textSelection.cursorIndex = value; + NotifyPropertyChanged(cursorIndexProperty); + } } /// /// The position of the text cursor inside the element. /// + [CreateProperty(ReadOnly = true)] public Vector2 cursorPosition { get => textSelection.cursorPosition; @@ -349,10 +434,17 @@ public Vector2 cursorPosition /// /// This is the selection index in the text presented. /// + [CreateProperty] public int selectIndex { get => textSelection.selectIndex; - set => textSelection.selectIndex = value; + set + { + if (textSelection.selectIndex == value) + return; + textSelection.selectIndex = value; + NotifyPropertyChanged(selectIndexProperty); + } } /// @@ -382,36 +474,65 @@ public void SelectRange(int cursorIndex, int selectionIndex) /// /// Controls whether the element's content is selected upon receiving focus. /// + [CreateProperty] public bool selectAllOnFocus { get => textSelection.selectAllOnFocus; - set => textSelection.selectAllOnFocus = value; + set + { + if (textSelection.selectAllOnFocus == value) + return; + textSelection.selectAllOnFocus = value; + NotifyPropertyChanged(selectAllOnFocusProperty); + } } /// /// Controls whether the element's content is selected when you mouse up for the first time. /// + [CreateProperty] public bool selectAllOnMouseUp { get => textSelection.selectAllOnMouseUp; - set => textSelection.selectAllOnMouseUp = value; + set + { + if (textSelection.selectAllOnMouseUp == value) + return; + textSelection.selectAllOnMouseUp = value; + NotifyPropertyChanged(selectAllOnMouseUpProperty); + } } /// /// Controls whether double clicking selects the word under the mouse pointer or not. /// + [CreateProperty] public bool doubleClickSelectsWord { get => textSelection.doubleClickSelectsWord; - set => textSelection.doubleClickSelectsWord = value; + set + { + if (textSelection.doubleClickSelectsWord == value) + return; + textSelection.doubleClickSelectsWord = value; + NotifyPropertyChanged(doubleClickSelectsWordProperty); + } } + /// /// Controls whether triple clicking selects the entire line under the mouse pointer or not. /// + [CreateProperty] public bool tripleClickSelectsLine { get => textSelection.tripleClickSelectsLine; - set => textSelection.tripleClickSelectsLine = value; + set + { + if (textSelection.tripleClickSelectsLine == value) + return; + textSelection.tripleClickSelectsLine = value; + NotifyPropertyChanged(tripleClickSelectsLineProperty); + } } /// @@ -435,10 +556,17 @@ public string text /// /// Option for controlling the visibility of the vertical scroll bar in the . /// + [CreateProperty] public ScrollerVisibility verticalScrollerVisibility { get => textInputBase.verticalScrollerVisibility; - set => textInputBase.SetVerticalScrollerVisibility(value); + set + { + if (textInputBase.verticalScrollerVisibility == value) + return; + textInputBase.SetVerticalScrollerVisibility(value); + NotifyPropertyChanged(verticalScrollerVisibilityProperty); + } } /// diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs index 6543122da..ea05b9321 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs @@ -4,6 +4,7 @@ using System; using System.Collections; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -106,6 +107,11 @@ namespace UnityEngine.UIElements /// public class ListView : BaseListView { + internal static readonly DataBindingProperty makeItemProperty = nameof(makeItem); + internal static readonly DataBindingProperty bindItemProperty = nameof(bindItem); + internal static readonly DataBindingProperty unbindItemProperty = nameof(unbindItem); + internal static readonly DataBindingProperty destroyItemProperty = nameof(destroyItem); + /// /// Instantiates a using data from a UXML file. /// @@ -137,6 +143,7 @@ public class ListView : BaseListView /// If this property and are not set, Unity will either create a PropertyField if bound /// to a SerializedProperty, or create an empty label for any other case. /// + [CreateProperty] public new Func makeItem { get => m_MakeItem; @@ -146,6 +153,7 @@ public class ListView : BaseListView { m_MakeItem = value; Rebuild(); + NotifyPropertyChanged(makeItemProperty); } } } @@ -167,6 +175,7 @@ internal void SetMakeItemWithoutNotify(Func func) /// If this property and are not set, Unity will try to bind to a SerializedProperty if /// bound, or simply set text in the created Label. /// + [CreateProperty] public new Action bindItem { get => m_BindItem; @@ -176,6 +185,7 @@ internal void SetMakeItemWithoutNotify(Func func) { m_BindItem = value; RefreshItems(); + NotifyPropertyChanged(bindItemProperty); } } } @@ -185,6 +195,7 @@ internal void SetBindItemWithoutNotify(Action callback) m_BindItem = callback; } + private Action m_UnbindItem; /// /// Callback for unbinding a data item from the VisualElement. /// @@ -192,7 +203,20 @@ internal void SetBindItemWithoutNotify(Action callback) /// The method called by this callback receives the VisualElement to unbind, and the index of the /// element to unbind it from. /// - public new Action unbindItem { get; set; } + [CreateProperty] + public new Action unbindItem + { + get => m_UnbindItem; + set + { + if (value == m_UnbindItem) + return; + m_UnbindItem = value; + NotifyPropertyChanged(unbindItemProperty); + } + } + + private Action m_DestroyItem; /// /// Callback invoked when a created via is no longer needed and will be destroyed. @@ -200,7 +224,18 @@ internal void SetBindItemWithoutNotify(Action callback) /// /// The method called by this callback receives the VisualElement that will be destroyed from the pool. /// - public new Action destroyItem { get; set; } + [CreateProperty] + public new Action destroyItem + { + get => m_DestroyItem; + set + { + if (value == m_DestroyItem) + return; + m_DestroyItem = value; + NotifyPropertyChanged(destroyItemProperty); + } + } internal override bool HasValidDataAndBindings() { diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/MinMaxSlider.cs b/ModuleOverrides/com.unity.ui/Core/Controls/MinMaxSlider.cs index 167435b0f..ef11a02be 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/MinMaxSlider.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/MinMaxSlider.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -11,6 +12,12 @@ namespace UnityEngine.UIElements /// public class MinMaxSlider : BaseField { + internal static readonly DataBindingProperty minValueProperty = nameof(minValue); + internal static readonly DataBindingProperty maxValueProperty = nameof(maxValue); + internal static readonly DataBindingProperty rangeProperty = nameof(range); + internal static readonly DataBindingProperty lowLimitProperty = nameof(lowLimit); + internal static readonly DataBindingProperty highLimitProperty = nameof(highLimit); + /// /// Instantiates a using the data read from a UXML file. /// @@ -77,12 +84,17 @@ private enum DragState /// /// This is the low value of the range represented on the slider. /// + [CreateProperty] public float minValue { get { return value.x; } set { + var previous = minValue; base.value = ClampValues(new Vector2(value, rawValue.y)); + + if (!Mathf.Approximately(previous, minValue)) + NotifyPropertyChanged(minValueProperty); } } @@ -90,12 +102,17 @@ public float minValue /// /// This is the high value of the range represented on the slider. /// + [CreateProperty] public float maxValue { get { return value.y; } set { + var previous = maxValue; base.value = ClampValues(new Vector2(rawValue.x, value)); + + if (!Mathf.Approximately(previous, maxValue)) + NotifyPropertyChanged(maxValueProperty); } } @@ -122,6 +139,7 @@ public override void SetValueWithoutNotify(Vector2 newValue) /// /// Returns the range of the low/high limits of the slider. /// + [CreateProperty(ReadOnly = true)] public float range { get { return Math.Abs(highLimit - lowLimit); } @@ -134,6 +152,7 @@ public float range /// /// This is the low limit of the slider. /// + [CreateProperty] public float lowLimit { get { return m_MinLimit; } @@ -152,6 +171,7 @@ public float lowLimit if (!string.IsNullOrEmpty(viewDataKey)) SaveViewData(); + NotifyPropertyChanged(lowLimitProperty); } } } @@ -160,6 +180,7 @@ public float lowLimit /// /// This is the high limit of the slider. /// + [CreateProperty] public float highLimit { get { return m_MaxLimit; } @@ -178,6 +199,7 @@ public float highLimit if (!string.IsNullOrEmpty(viewDataKey)) SaveViewData(); + NotifyPropertyChanged(highLimitProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/PopupField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/PopupField.cs index c4a6007e7..6ad21a671 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/PopupField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/PopupField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.UIElements @@ -14,6 +15,8 @@ namespace UnityEngine.UIElements [MovedFrom(true, UpgradeConstants.EditorNamespace, UpgradeConstants.EditorAssembly)] public class PopupField : BasePopupField { + internal static readonly DataBindingProperty indexProperty = nameof(index); + /// /// Callback that provides a string representation used to display the selected value. /// @@ -58,8 +61,11 @@ public override T value get { return base.value; } set { - m_Index = m_Choices?.IndexOf(value) ?? -1; + var newIndex = m_Choices?.IndexOf(value) ?? -1; + m_Index = newIndex; base.value = value; + if (m_Index != newIndex) + NotifyPropertyChanged(indexProperty); } } @@ -74,6 +80,7 @@ public override void SetValueWithoutNotify(T newValue) /// /// The currently selected index in the popup menu. /// + [CreateProperty] public int index { get { return m_Index; } @@ -86,6 +93,7 @@ public int index this.value = m_Choices[m_Index]; else this.value = default(T); + NotifyPropertyChanged(indexProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/ProgressBar.cs b/ModuleOverrides/com.unity.ui/Core/Controls/ProgressBar.cs index 2772ef5d8..04720ceca 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/ProgressBar.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/ProgressBar.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System.Collections.Generic; +using Unity.Properties; using UnityEngine; using UnityEngine.Scripting.APIUpdating; @@ -13,6 +14,11 @@ namespace UnityEngine.UIElements /// public abstract class AbstractProgressBar : BindableElement, INotifyValueChanged { + internal static readonly DataBindingProperty titleProperty = nameof(title); + internal static readonly DataBindingProperty lowValueProperty = nameof(lowValue); + internal static readonly DataBindingProperty highValueProperty = nameof(highValue); + internal static readonly DataBindingProperty valueProperty = nameof(value); + /// /// USS Class Name used to style the . /// @@ -67,35 +73,54 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// Sets the title of the ProgressBar that displays in the center of the control. /// + [CreateProperty] public string title { get => m_Title.text; - set => m_Title.text = value; + set + { + var previous = title; + m_Title.text = value; + + if (string.CompareOrdinal(previous, title) != 0) + NotifyPropertyChanged(titleProperty); + } } /// /// Sets the minimum value of the ProgressBar. /// + [CreateProperty] public float lowValue { get => m_LowValue; set { + var previous = lowValue; m_LowValue = value; SetProgress(m_Value); + + if (!Mathf.Approximately(previous, lowValue)) + NotifyPropertyChanged(lowValueProperty); } } /// /// Sets the maximum value of the ProgressBar. /// + [CreateProperty] public float highValue { get => m_HighValue; set { + var previous = highValue; + m_HighValue = value; SetProgress(m_Value); + + if (!Mathf.Approximately(previous, highValue)) + NotifyPropertyChanged(highValueProperty); } } @@ -138,6 +163,7 @@ void OnGeometryChanged(GeometryChangedEvent e) /// /// Sets the progress value. If the value has changed, dispatches an of type float. /// + [CreateProperty] public virtual float value { get { return m_Value; } @@ -152,6 +178,7 @@ public virtual float value evt.elementTarget = this; SetValueWithoutNotify(value); SendEvent(evt); + NotifyPropertyChanged(valueProperty); } } else diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/RadioButtonGroup.cs b/ModuleOverrides/com.unity.ui/Core/Controls/RadioButtonGroup.cs index de230ff0f..04522a143 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/RadioButtonGroup.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/RadioButtonGroup.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -12,6 +13,8 @@ namespace UnityEngine.UIElements /// public class RadioButtonGroup : BaseField, IGroupBox { + internal static readonly DataBindingProperty choicesProperty = nameof(choices); + /// /// Instantiates a using data from a UXML file. /// @@ -35,7 +38,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext base.Init(ve, bag, cc); var f = (RadioButtonGroup)ve; - f.choices = ParseChoiceList(m_Choices.GetValueFromBag(bag, cc)); + f.choices = UxmlUtility.ParseStringListAttribute(m_Choices.GetValueFromBag(bag, cc)); } } @@ -62,6 +65,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// Writing to this property removes existing elements and /// re-creates them to display the new list. /// + [CreateProperty] public IEnumerable choices { get @@ -107,6 +111,7 @@ public IEnumerable choices } UpdateRadioButtons(); + NotifyPropertyChanged(choicesProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs index 9aaf19363..3a9b1f86e 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/ScrollView.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -99,6 +101,17 @@ public enum ScrollerVisibility /// public class ScrollView : VisualElement { + internal static readonly DataBindingProperty horizontalScrollerVisibilityProperty = nameof(horizontalScrollerVisibility); + internal static readonly DataBindingProperty verticalScrollerVisibilityProperty = nameof(verticalScrollerVisibility); + internal static readonly DataBindingProperty scrollOffsetProperty = nameof(scrollOffset); + internal static readonly DataBindingProperty horizontalPageSizeProperty = nameof(horizontalPageSize); + internal static readonly DataBindingProperty verticalPageSizeProperty = nameof(verticalPageSize); + internal static readonly DataBindingProperty scrollDecelerationRateProperty = nameof(scrollDecelerationRate); + internal static readonly DataBindingProperty elasticityProperty = nameof(elasticity); + internal static readonly DataBindingProperty touchScrollBehaviorProperty = nameof(touchScrollBehavior); + internal static readonly DataBindingProperty nestedInteractionKindProperty = nameof(nestedInteractionKind); + internal static readonly DataBindingProperty modeProperty = nameof(mode); + /// /// Instantiates a using the data read from a UXML file. /// @@ -187,13 +200,18 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// Specifies whether the horizontal scroll bar is visible. /// + [CreateProperty] public ScrollerVisibility horizontalScrollerVisibility { get { return m_HorizontalScrollerVisibility; } set { + var previous = m_HorizontalScrollerVisibility; m_HorizontalScrollerVisibility = value; UpdateScrollers(needsHorizontal, needsVertical); + + if (previous != m_HorizontalScrollerVisibility) + NotifyPropertyChanged(horizontalScrollerVisibilityProperty); } } @@ -202,13 +220,17 @@ public ScrollerVisibility horizontalScrollerVisibility /// /// Specifies whether the vertical scroll bar is visible. /// + [CreateProperty] public ScrollerVisibility verticalScrollerVisibility { get { return m_VerticalScrollerVisibility; } set { + var previous = m_VerticalScrollerVisibility; m_VerticalScrollerVisibility = value; UpdateScrollers(needsHorizontal, needsVertical); + if (previous != m_VerticalScrollerVisibility) + NotifyPropertyChanged(verticalScrollerVisibilityProperty); } } @@ -280,6 +302,7 @@ internal bool isHorizontalScrollDisplayed /// /// The current scrolling position. /// + [CreateProperty] public Vector2 scrollOffset { get { return new Vector2(horizontalScroller.value, verticalScroller.value); } @@ -290,6 +313,7 @@ public Vector2 scrollOffset horizontalScroller.value = value.x; verticalScroller.value = value.y; UpdateContentViewTransform(); + NotifyPropertyChanged(scrollOffsetProperty); } } } @@ -299,13 +323,17 @@ public Vector2 scrollOffset /// /// This property is controlling the scrolling speed of the horizontal scroller. /// + [CreateProperty] public float horizontalPageSize { get { return m_HorizontalPageSize; } set { + var previous = m_HorizontalPageSize; m_HorizontalPageSize = value; UpdateHorizontalSliderPageSize(); + if (!Mathf.Approximately(previous, m_HorizontalPageSize)) + NotifyPropertyChanged(horizontalPageSizeProperty); } } @@ -314,13 +342,17 @@ public float horizontalPageSize /// /// This property is controlling the scrolling speed of the vertical scroller. /// + [CreateProperty] public float verticalPageSize { get { return m_VerticalPageSize; } set { + var previous = m_VerticalPageSize; m_VerticalPageSize = value; UpdateVerticalSliderPageSize(); + if (!Mathf.Approximately(previous, m_VerticalPageSize)) + NotifyPropertyChanged(verticalPageSizeProperty); } } @@ -344,10 +376,17 @@ internal float scrollableHeight /// /// The deceleration rate is the speed reduction per second. A value of 0.5 halves the speed each second. A value of 0 stops the scrolling immediately. /// + [CreateProperty] public float scrollDecelerationRate { get { return m_ScrollDecelerationRate; } - set { m_ScrollDecelerationRate = Mathf.Max(0f, value); } + set + { + var previous = m_ScrollDecelerationRate; + m_ScrollDecelerationRate = Mathf.Max(0f, value); + if (!Mathf.Approximately(previous, m_ScrollDecelerationRate)) + NotifyPropertyChanged(scrollDecelerationRateProperty); + } } // For elastic behavior: how long it takes to go back to original position. @@ -359,10 +398,17 @@ public float scrollDecelerationRate /// /// Elasticity is only used when is set to Elastic. /// + [CreateProperty] public float elasticity { get { return m_Elasticity;} - set { m_Elasticity = Mathf.Max(0f, value); } + set + { + var previous = m_Elasticity; + m_Elasticity = Mathf.Max(0f, value); + if (!Mathf.Approximately(previous, m_Elasticity)) + NotifyPropertyChanged(elasticityProperty); + } } /// @@ -388,11 +434,13 @@ public enum TouchScrollBehavior /// /// The behavior to use when a user tries to scroll past the boundaries of the ScrollView content using a touch interaction. /// + [CreateProperty] public TouchScrollBehavior touchScrollBehavior { get { return m_TouchScrollBehavior; } set { + var previous = m_TouchScrollBehavior; m_TouchScrollBehavior = value; if (m_TouchScrollBehavior == TouchScrollBehavior.Clamped) { @@ -404,6 +452,8 @@ public TouchScrollBehavior touchScrollBehavior horizontalScroller.slider.clamped = false; verticalScroller.slider.clamped = false; } + if (previous != m_TouchScrollBehavior) + NotifyPropertyChanged(touchScrollBehaviorProperty); } } @@ -433,10 +483,17 @@ public enum NestedInteractionKind /// /// The behavior to use when scrolling reaches limits of a nested . /// + [CreateProperty] public NestedInteractionKind nestedInteractionKind { get => m_NestedInteractionKind; - set => m_NestedInteractionKind = value; + set + { + var previous = m_NestedInteractionKind; + m_NestedInteractionKind = value; + if (previous != m_NestedInteractionKind) + NotifyPropertyChanged(nestedInteractionKindProperty); + } } void OnHorizontalScrollDragElementChanged(GeometryChangedEvent evt) @@ -611,16 +668,16 @@ private float GetDeltaDistance(float viewMin, float viewMax, float childBoundary /// /// Represents the visible part of contentContainer. /// - public VisualElement contentViewport { get; private set; } // Represents the visible part of contentContainer + public VisualElement contentViewport { get; } // Represents the visible part of contentContainer /// /// Horizontal scrollbar. /// - public Scroller horizontalScroller { get; private set; } + public Scroller horizontalScroller { get; } /// /// Vertical Scrollbar. /// - public Scroller verticalScroller { get; private set; } + public Scroller verticalScroller { get; } private VisualElement m_ContentContainer; private VisualElement m_ContentAndVerticalScrollContainer; @@ -798,6 +855,7 @@ public ScrollView(ScrollViewMode scrollViewMode) /// . When the value changes, the class list matching the old value is removed and /// the class list matching the new value is added. /// + [CreateProperty] public ScrollViewMode mode { get => m_Mode; @@ -806,6 +864,7 @@ public ScrollViewMode mode if (m_Mode == value) return; SetScrollViewMode(value); + NotifyPropertyChanged(modeProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/Scroller.cs b/ModuleOverrides/com.unity.ui/Core/Controls/Scroller.cs index 0fa1e28b4..1d1ee8b47 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/Scroller.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/Scroller.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -12,6 +13,11 @@ namespace UnityEngine.UIElements /// public class Scroller : VisualElement { + internal static readonly DataBindingProperty valueProperty = nameof(value); + internal static readonly DataBindingProperty lowValueProperty = nameof(lowValue); + internal static readonly DataBindingProperty highValueProperty = nameof(highValue); + internal static readonly DataBindingProperty directionProperty = nameof(direction); + class ScrollerSlider : Slider { public ScrollerSlider(float start, float end, @@ -78,51 +84,78 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// The slider used by this scroller. /// - public Slider slider { get; private set; } + public Slider slider { get; } + /// /// Bottom or left scroll button. /// - public RepeatButton lowButton { get; private set; } + public RepeatButton lowButton { get; } + /// /// Top or right scroll button. /// - public RepeatButton highButton { get; private set; } + public RepeatButton highButton { get; } /// /// Value that defines the slider position. It lies between and . /// + [CreateProperty] public float value { get { return slider.value; } - set { slider.value = value; } + set + { + var previous = slider.value; + slider.value = value; + if (!Mathf.Approximately(previous, slider.value)) + NotifyPropertyChanged(valueProperty); + } } /// /// Minimum value. /// + [CreateProperty] public float lowValue { get { return slider.lowValue; } - set { slider.lowValue = value; } + set + { + var previous = slider.lowValue; + slider.lowValue = value; + + if (!Mathf.Approximately(previous, slider.lowValue)) + NotifyPropertyChanged(lowValueProperty); + } } /// /// Maximum value. /// + [CreateProperty] public float highValue { get { return slider.highValue; } - set { slider.highValue = value; } + set + { + var previous = slider.highValue; + slider.highValue = value; + + if (!Mathf.Approximately(previous, slider.highValue)) + NotifyPropertyChanged(highValueProperty); + } } /// /// Direction of this scrollbar. /// + [CreateProperty] public SliderDirection direction { get { return resolvedStyle.flexDirection == FlexDirection.Row ? SliderDirection.Horizontal : SliderDirection.Vertical; } set { + var previous = slider.direction; slider.direction = value; // We want default behavior for vertical scrollers to be lowValue at the top and highValue at the bottom, // instead of the default Slider behavior. @@ -139,6 +172,8 @@ public SliderDirection direction AddToClassList(verticalVariantUssClassName); RemoveFromClassList(horizontalVariantUssClassName); } + if (previous != slider.direction) + NotifyPropertyChanged(directionProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/TextValueField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/TextValueField.cs index bfe387187..9a6df3fdb 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/TextValueField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/TextValueField.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.UIElements @@ -62,6 +63,8 @@ public interface IValueField [MovedFrom(true, UpgradeConstants.EditorNamespace, UpgradeConstants.EditorAssembly)] public abstract class TextValueField : TextInputBaseField, IValueField { + internal static readonly DataBindingProperty formatStringProperty = nameof(formatString); + // This property to alleviate the fact we have to cast all the time TextValueInput textValueInput => (TextValueInput)textInputBase; @@ -72,6 +75,7 @@ public abstract class TextValueField : TextInputBaseField /// The format string for the value. /// + [CreateProperty] public string formatString { get => textValueInput.formatString; @@ -81,6 +85,7 @@ public string formatString { textValueInput.formatString = value; textEdition.UpdateText(ValueToString(rawValue)); + NotifyPropertyChanged(formatStringProperty); } } } @@ -124,15 +129,6 @@ public void StopDragging() textValueInput.StopDragging(); } - /// - /// This is the value of the field. - /// - public override TValueType value - { - get => base.value; - set => base.value = value; - } - internal override void UpdateValueFromText() { // Prevent text from changing when the value change diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/TreeView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/TreeView.cs index c758a3d26..6ff20df99 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/TreeView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/TreeView.cs @@ -3,9 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -34,6 +33,11 @@ namespace UnityEngine.UIElements /// public class TreeView : BaseTreeView { + internal static readonly DataBindingProperty makeItemProperty = nameof(makeItem); + internal static readonly DataBindingProperty bindItemProperty = nameof(bindItem); + internal static readonly DataBindingProperty unbindItemProperty = nameof(unbindItem); + internal static readonly DataBindingProperty destroyItemProperty = nameof(destroyItem); + /// /// Instantiates a using data from a UXML file. /// @@ -65,6 +69,7 @@ public class TreeView : BaseTreeView /// If this property and are not set, Unity will either create a PropertyField if bound /// to a SerializedProperty, or create an empty label for any other case. /// + [CreateProperty] public new Func makeItem { get => m_MakeItem; @@ -74,6 +79,7 @@ public class TreeView : BaseTreeView { m_MakeItem = value; Rebuild(); + NotifyPropertyChanged(makeItemProperty); } } } @@ -90,6 +96,7 @@ public class TreeView : BaseTreeView /// If this property and are not set, Unity will try to bind to a SerializedProperty if /// bound, or simply set text in the created Label. /// + [CreateProperty] public new Action bindItem { get => m_BindItem; @@ -99,11 +106,13 @@ public class TreeView : BaseTreeView { m_BindItem = value; RefreshItems(); + NotifyPropertyChanged(bindItemProperty); } - } } + private Action m_UnbindItem; + /// /// Callback for unbinding a data item from the VisualElement. /// @@ -111,7 +120,21 @@ public class TreeView : BaseTreeView /// The method called by this callback receives the VisualElement to unbind, and the index of the /// element to unbind it from. /// - public new Action unbindItem { get; set; } + [CreateProperty] + public new Action unbindItem + { + get => m_UnbindItem; + set + { + if (value != m_UnbindItem) + { + m_UnbindItem = value; + NotifyPropertyChanged(unbindItemProperty); + } + } + } + + private Action m_DestroyItem; /// /// Callback invoked when a created via is no longer needed and will be destroyed. @@ -119,7 +142,20 @@ public class TreeView : BaseTreeView /// /// The method called by this callback receives the VisualElement that will be destroyed from the pool. /// - public new Action destroyItem { get; set; } + [CreateProperty] + public new Action destroyItem + { + get => m_DestroyItem; + set + { + if (value != m_DestroyItem) + { + m_DestroyItem = value; + NotifyPropertyChanged(destroyItemProperty); + } + } + } + /// /// Sets the root items. diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs index 68b90589a..14d436457 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -11,6 +12,10 @@ namespace UnityEngine.UIElements /// public class TwoPaneSplitView : VisualElement { + internal static readonly DataBindingProperty fixedPaneIndexProperty = nameof(fixedPaneIndex); + internal static readonly DataBindingProperty fixedPaneInitialDimensionProperty = nameof(fixedPaneInitialDimension); + internal static readonly DataBindingProperty orientationProperty = nameof(orientation); + static readonly string s_UssClassName = "unity-two-pane-split-view"; static readonly string s_ContentContainerClassName = "unity-two-pane-split-view__content-container"; static readonly string s_HandleDragLineClassName = "unity-two-pane-split-view__dragline"; @@ -58,7 +63,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext VisualElement m_FixedPane; VisualElement m_FlexedPane; - [SerializeField] float m_FixedPaneDimension = -1; + [SerializeField, DontCreateProperty] float m_FixedPaneDimension = -1; /// /// The child element that is set as the fixed size pane. @@ -83,6 +88,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// /// 0 for setting first child as the fixed pane, 1 for the second child element. /// + [CreateProperty] public int fixedPaneIndex { get => m_FixedPaneIndex; @@ -92,12 +98,14 @@ public int fixedPaneIndex return; Init(value, m_FixedPaneInitialDimension, m_Orientation); + NotifyPropertyChanged(fixedPaneIndexProperty); } } /// /// The initial width or height for the fixed pane. /// + [CreateProperty] public float fixedPaneInitialDimension { get => m_FixedPaneInitialDimension; @@ -107,12 +115,14 @@ public float fixedPaneInitialDimension return; Init(m_FixedPaneIndex, value, m_Orientation); + NotifyPropertyChanged(fixedPaneInitialDimensionProperty); } } /// /// Orientation of the split view. /// + [CreateProperty] public TwoPaneSplitViewOrientation orientation { get => m_Orientation; @@ -122,6 +132,7 @@ public TwoPaneSplitViewOrientation orientation return; Init(m_FixedPaneIndex, m_FixedPaneInitialDimension, value); + NotifyPropertyChanged(orientationProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Conversions/UIConversion.cs b/ModuleOverrides/com.unity.ui/Core/Conversions/UIConversion.cs index 7bd1faa45..6169b94d3 100644 --- a/ModuleOverrides/com.unity.ui/Core/Conversions/UIConversion.cs +++ b/ModuleOverrides/com.unity.ui/Core/Conversions/UIConversion.cs @@ -16,6 +16,9 @@ internal static class UIConversion static readonly ConversionRegistry s_GlobalUIConverters = ConversionRegistry.Create(); static readonly ConversionRegistry s_PrimitiveConverters = ConversionRegistry.Create(); + internal static ConversionRegistry globalUIConverters => s_GlobalUIConverters; + internal static ConversionRegistry primitiveConverters => s_PrimitiveConverters; + static UIConversion() { RegisterPrimitivesConverter(); @@ -175,6 +178,7 @@ static void RegisterPrimitivesConverter() RegisterDoubleConverters(); RegisterBooleanConverters(); RegisterCharConverters(); + RegisterColorConverters(); } static void RegisterInt8Converters() @@ -467,5 +471,11 @@ static void RegisterCharConverters() s_PrimitiveConverters.Register(typeof(string), typeof(char), (TypeConverter) ((ref string v) => !string.IsNullOrEmpty(v) ? v[0] : '\0')); } + + static void RegisterColorConverters() + { + s_PrimitiveConverters.Register(typeof(Color), typeof(Color32), (TypeConverter) ((ref Color v) => v)); + s_PrimitiveConverters.Register(typeof(Color32), typeof(Color), (TypeConverter) ((ref Color32 v) => v)); + } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Events/EventHandler.cs b/ModuleOverrides/com.unity.ui/Core/Events/EventHandler.cs index 41e81c624..4a1a946aa 100644 --- a/ModuleOverrides/com.unity.ui/Core/Events/EventHandler.cs +++ b/ModuleOverrides/com.unity.ui/Core/Events/EventHandler.cs @@ -230,6 +230,15 @@ internal virtual void ExecuteDefaultActionDisabled(EventBase evt) {} internal const string ExecuteDefaultActionName = nameof(ExecuteDefaultAction); internal const string ExecuteDefaultActionAtTargetName = nameof(ExecuteDefaultActionAtTarget); + /// + /// Informs the data binding system that a property of a control has changed. + /// + /// The property that has changed. + internal void NotifyPropertyChanged(DataBindingProperty property) + { + // Intentionally left empty. This will be implemented with the data binding feature. + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ExecuteDefaultActionInternal(EventBase evt) => ExecuteDefaultAction(evt); diff --git a/ModuleOverrides/com.unity.ui/Core/Events/KeyboardEvents.cs b/ModuleOverrides/com.unity.ui/Core/Events/KeyboardEvents.cs index 744b8ded6..a39115440 100644 --- a/ModuleOverrides/com.unity.ui/Core/Events/KeyboardEvents.cs +++ b/ModuleOverrides/com.unity.ui/Core/Events/KeyboardEvents.cs @@ -336,6 +336,7 @@ internal static class KeyboardEventExtensions internal static bool ShouldSendNavigationMoveEvent(this KeyDownEvent e) { // We must rely on KeyCode.Tab as Shift+Tab on Mac doesn't send the \t character. It sends char(25) instead. + // On linux Platform shift+tab event does contain both key code and character=='\t' return e.keyCode == KeyCode.Tab && !e.ctrlKey && !e.altKey && !e.commandKey && !e.functionKey; } diff --git a/ModuleOverrides/com.unity.ui/Core/FocusController.cs b/ModuleOverrides/com.unity.ui/Core/FocusController.cs index 45b1e5c09..5427ac5ba 100644 --- a/ModuleOverrides/com.unity.ui/Core/FocusController.cs +++ b/ModuleOverrides/com.unity.ui/Core/FocusController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -15,6 +16,12 @@ namespace UnityEngine.UIElements /// public abstract class Focusable : CallbackEventHandler { + internal static readonly DataBindingProperty focusableProperty = nameof(focusable); + internal static readonly DataBindingProperty tabIndexProperty = nameof(tabIndex); + internal static readonly DataBindingProperty delegatesFocusProperty = nameof(delegatesFocus); + internal static readonly DataBindingProperty canGrabFocusProperty = nameof(canGrabFocus); + + protected Focusable() { focusable = true; @@ -26,27 +33,58 @@ protected Focusable() /// public abstract FocusController focusController { get; } + private bool m_Focusable; + /// /// True if the element can be focused. /// - public bool focusable { get; set; } + [CreateProperty] + public bool focusable + { + get => m_Focusable; + set + { + if (m_Focusable == value) + return; + m_Focusable = value; + NotifyPropertyChanged(focusableProperty); + } + } + + private int m_TabIndex; // See http://w3c.github.io/html/editing.html#the-tabindex-attribute /// /// An integer used to sort focusables in the focus ring. Must be greater than or equal to zero. /// - public int tabIndex { get; set; } + [CreateProperty] + public int tabIndex + { + get => m_TabIndex; + set + { + if (m_TabIndex == value) + return; + m_TabIndex = value; + NotifyPropertyChanged(tabIndexProperty); + } + } bool m_DelegatesFocus; + /// /// Whether the element should delegate the focus to its children. /// + [CreateProperty] public bool delegatesFocus { get { return m_DelegatesFocus; } set { + if (m_DelegatesFocus == value) + return; m_DelegatesFocus = value; + NotifyPropertyChanged(delegatesFocusProperty); } } @@ -70,6 +108,7 @@ internal bool excludeFromFocusRing /// /// Return true if the element can be focused. /// + [CreateProperty(ReadOnly = true)] public virtual bool canGrabFocus => focusable; /// @@ -555,7 +594,7 @@ internal void ReevaluateFocus() if (focusedElement is VisualElement currentFocus) { // If the currently focused element is not displayed in the hierarchy or not visible, blur it. - if (!currentFocus.isHierarchyDisplayed || !currentFocus.visible) + if (!currentFocus.areAncestorsAndSelfDisplayed || !currentFocus.visible) currentFocus.Blur(); } } diff --git a/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs b/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs index fc975ba06..afe4ddc17 100644 --- a/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs +++ b/ModuleOverrides/com.unity.ui/Core/IMGUIContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Unity.Profiling; +using Unity.Properties; using UnityEngine.UIElements.Experimental; namespace UnityEngine.UIElements @@ -14,6 +15,9 @@ namespace UnityEngine.UIElements /// public class IMGUIContainer : VisualElement, IDisposable { + internal static readonly DataBindingProperty cullingEnabledProperty = nameof(cullingEnabled); + internal static readonly DataBindingProperty contextTypeProperty = nameof(contextType); + /// /// Instantiates an using the data read from a UXML file. /// @@ -92,10 +96,18 @@ internal ObjectGUIState guiState /// /// When this property is set to true, is not called when the Element is outside the viewport. /// + [CreateProperty] public bool cullingEnabled { get { return m_CullingEnabled; } - set { m_CullingEnabled = value; IncrementVersion(VersionChangeType.Repaint); } + set + { + if (m_CullingEnabled == value) + return; + m_CullingEnabled = value; + IncrementVersion(VersionChangeType.Repaint); + NotifyPropertyChanged(cullingEnabledProperty); + } } private bool m_RefreshCachedLayout = true; @@ -134,10 +146,23 @@ private float layoutMeasuredHeight } } + private ContextType m_ContextType; + /// - /// ContextType of this IMGUIContrainer. Currently only supports ContextType.Editor. + /// ContextType of this IMGUIContainer. Currently only supports ContextType.Editor. /// - public ContextType contextType { get; set; } + [CreateProperty] + public ContextType contextType + { + get => m_ContextType; + set + { + if (m_ContextType == value) + return; + m_ContextType = value; + NotifyPropertyChanged(contextTypeProperty); + } + } // The following 2 flags indicate the following : // 1) lostFocus : a blur event occurred and we need to make sure the actual keyboard focus from IMGUI is really un-focused diff --git a/ModuleOverrides/com.unity.ui/Core/ImmediateModeElement.cs b/ModuleOverrides/com.unity.ui/Core/ImmediateModeElement.cs index 1cbe48773..3694179a6 100644 --- a/ModuleOverrides/com.unity.ui/Core/ImmediateModeElement.cs +++ b/ModuleOverrides/com.unity.ui/Core/ImmediateModeElement.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Unity.Profiling; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -16,6 +17,8 @@ namespace UnityEngine.UIElements /// public abstract class ImmediateModeElement : VisualElement { + internal static readonly DataBindingProperty cullingEnabledProperty = nameof(cullingEnabled); + static readonly Dictionary s_Markers = new Dictionary(); readonly ProfilerMarker m_ImmediateRepaintMarker; @@ -24,10 +27,18 @@ public abstract class ImmediateModeElement : VisualElement /// /// When this property is set to true, the Element does not repaint itself when it is outside the viewport. /// + [CreateProperty] public bool cullingEnabled { get { return m_CullingEnabled; } - set { m_CullingEnabled = value; IncrementVersion(VersionChangeType.Repaint); } + set + { + if (m_CullingEnabled == value) + return; + m_CullingEnabled = value; + IncrementVersion(VersionChangeType.Repaint); + NotifyPropertyChanged(cullingEnabledProperty); + } } /// diff --git a/ModuleOverrides/com.unity.ui/Core/Panel.cs b/ModuleOverrides/com.unity.ui/Core/Panel.cs index b9db05510..c7f14f247 100644 --- a/ModuleOverrides/com.unity.ui/Core/Panel.cs +++ b/ModuleOverrides/com.unity.ui/Core/Panel.cs @@ -58,6 +58,8 @@ internal enum VersionChangeType TransitionProperty = 1 << 15, // The combined registered callbacks' EventCategory values has changed EventCallbackCategories = 1 << 16, + // The DisableRendering flag has changed + DisableRendering = 1 << 17, } /// diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRCommandManipulator.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRCommandManipulator.cs index ef8cc687b..dca00afe9 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRCommandManipulator.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRCommandManipulator.cs @@ -46,7 +46,7 @@ public static void ReplaceCommands(RenderChain renderChain, VisualElement ve, En if (processor.firstHeadCommand != null) { if (!foundInsertionBounds) - FindCommandInsertionPoint(ve, out prev, out next); + FindHeadCommandInsertionPoint(ve, out prev, out next); if (prev != null) { @@ -107,12 +107,12 @@ public static void ReplaceCommands(RenderChain renderChain, VisualElement ve, En } } - static void FindCommandInsertionPoint(VisualElement ve, out RenderChainCommand prev, out RenderChainCommand next) + static void FindHeadCommandInsertionPoint(VisualElement ve, out RenderChainCommand prev, out RenderChainCommand next) { VisualElement prevDrawingElem = ve.renderChainData.prev; // This can be potentially O(n) of VE count - // It is ok to check against lastCommand to mean the presence of tailCommand too, as we + // It is ok to check against lastHeadCommand to mean the presence of tailCommand too, as we // require that tail commands only exist if a head command exists too while (prevDrawingElem != null && prevDrawingElem.renderChainData.lastHeadCommand == null) prevDrawingElem = prevDrawingElem.renderChainData.prev; @@ -132,7 +132,7 @@ static void FindCommandInsertionPoint(VisualElement ve, out RenderChainCommand p // Case C, get the last command that isn't owned by us, this is to skip potential // tail commands wrapped after the previous drawing element var lastCommand = prevDrawingElem.renderChainData.lastTailOrHeadCommand; - for (;;) + for (; ; ) { prev = lastCommand; lastCommand = lastCommand.next; @@ -167,7 +167,7 @@ static void FindTailCommandInsertionPoint(VisualElement ve, out RenderChainComma // Depth first search for the first VE that has a command (i.e. non empty element). // This can be potentially O(n) of VE count - // It is ok to check against lastCommand to mean the presence of tailCommand too, as we + // It is ok to check against lastHeadCommand to mean the presence of tailCommand too, as we // require that tail commands only exist if a startup command exists too while (nextDrawingElem != null && nextDrawingElem.renderChainData.firstHeadCommand == null) nextDrawingElem = nextDrawingElem.renderChainData.next; @@ -186,7 +186,7 @@ static void FindTailCommandInsertionPoint(VisualElement ve, out RenderChainComma else if (ve.IsParentOrAncestorOf(nextDrawingElem)) // Case B { // Enclose the last deepest drawing child by our tail command - for (;;) + for (; ; ) { prev = nextDrawingElem.renderChainData.lastTailOrHeadCommand; nextDrawingElem = prev.next?.owner; @@ -281,5 +281,169 @@ public static void ResetCommands(RenderChain renderChain, VisualElement ve) } ve.renderChainData.firstTailCommand = ve.renderChainData.lastTailCommand = null; } + + static void InjectCommandInBetween(RenderChain renderChain, RenderChainCommand cmd, RenderChainCommand prev, RenderChainCommand next) + { + if (prev != null) + { + cmd.prev = prev; + prev.next = cmd; + } + if (next != null) + { + cmd.next = next; + next.prev = cmd; + } + VisualElement ve = cmd.owner; + + if (!cmd.isTail) + { + if (ve.renderChainData.firstHeadCommand == null || ve.renderChainData.firstHeadCommand == next) + ve.renderChainData.firstHeadCommand = cmd; + + if (ve.renderChainData.lastHeadCommand == null || ve.renderChainData.lastHeadCommand == prev) + ve.renderChainData.lastHeadCommand = cmd; + } + else + { + if (ve.renderChainData.firstTailCommand == null || ve.renderChainData.firstTailCommand == next) + ve.renderChainData.firstTailCommand = cmd; + + if (ve.renderChainData.lastTailCommand == null || ve.renderChainData.lastTailCommand == prev) + ve.renderChainData.lastTailCommand = cmd; + } + renderChain.OnRenderCommandAdded(cmd); + + } + + public static void DisableElementRendering(RenderChain renderChain, VisualElement ve, bool renderingDisabled) + { + if (!ve.renderChainData.isInChain) + return; + + if (renderingDisabled) + { + if (ve.renderChainData.firstHeadCommand == null || ve.renderChainData.firstHeadCommand.type != CommandType.BeginDisable) + { + var cmd = renderChain.AllocCommand(); + cmd.type = CommandType.BeginDisable; + cmd.owner = ve; + + if (ve.renderChainData.firstHeadCommand == null) + { + FindHeadCommandInsertionPoint(ve, out var cmdPrev, out var cmdNext); + InjectCommandInBetween(renderChain, cmd, cmdPrev, cmdNext); + } + else + { + // Need intermediate variable to pass by reference as it is modified + var prev = ve.renderChainData.firstHeadCommand.prev; + var next = ve.renderChainData.firstHeadCommand; + var lastHeadCommand = ve.renderChainData.lastHeadCommand; // InjectCommandInBetween assumes we are adding the last command, witch is not the case now. Backup the value to restore after. + Debug.Assert(lastHeadCommand != null); + ve.renderChainData.firstHeadCommand = null; // will be replaced in InjectCommandInBetween + InjectCommandInBetween(renderChain, cmd, prev, next); + ve.renderChainData.lastHeadCommand = lastHeadCommand; + } + } + + if (ve.renderChainData.lastTailCommand == null || ve.renderChainData.lastTailCommand.type != CommandType.EndDisable) + { + var cmd = renderChain.AllocCommand(); + cmd.type = CommandType.EndDisable; + cmd.isTail = true; + cmd.owner = ve; + + if (ve.renderChainData.lastTailCommand == null) + { + FindTailCommandInsertionPoint(ve, out var cmdPrev, out var cmdNext); + InjectCommandInBetween(renderChain, cmd, cmdPrev, cmdNext); + } + else + { + // Need intermediate variable to pass by reference as it is modified + var prev = ve.renderChainData.lastTailCommand; + var next = ve.renderChainData.lastTailCommand.next; + Debug.Assert(ve.renderChainData.firstTailCommand != null); + InjectCommandInBetween(renderChain, cmd, prev, next); + } + } + } + else + { + + if (ve.renderChainData.firstHeadCommand != null && ve.renderChainData.firstHeadCommand.type == CommandType.BeginDisable) + RemoveSingleCommand(renderChain, ve, ve.renderChainData.firstHeadCommand); + + if (ve.renderChainData.lastTailCommand != null && ve.renderChainData.lastTailCommand.type == CommandType.EndDisable) + RemoveSingleCommand(renderChain, ve, ve.renderChainData.lastTailCommand); + } + + } + + static void RemoveSingleCommand(RenderChain renderChain, VisualElement ve, RenderChainCommand cmd) + { + Debug.Assert(cmd != null); + Debug.Assert(cmd.owner == ve); + renderChain.OnRenderCommandsRemoved(cmd, cmd); + var prev = cmd.prev; + var next = cmd.next; + if (prev != null) prev.next = next; + if (next != null) next.prev = prev; + + // Clean up renderChain head commands pointers in the VisualElement's renderChainData + if (ve.renderChainData.firstHeadCommand == cmd) + { + // is this the last Head command of the object + if (ve.renderChainData.firstHeadCommand == ve.renderChainData.lastHeadCommand) + { + // Last command removed: extra checks + Debug.Assert(cmd.prev?.owner != ve, "When removing the first head command, the command before this one in the queue should belong to an other parent"); + Debug.Assert(cmd.next?.owner != ve || cmd.next == ve.renderChainData.firstTailCommand); // It could be valid that there is a closing command if they get removed after this call. + ve.renderChainData.firstHeadCommand = null; + ve.renderChainData.lastHeadCommand = null; + } + else + { + Debug.Assert(cmd.next.owner == ve); + Debug.Assert(ve.renderChainData.lastHeadCommand != null); + ve.renderChainData.firstHeadCommand = cmd.next; + } + } + else if (ve.renderChainData.lastHeadCommand == cmd) + { + Debug.Assert(cmd.prev.owner == ve); + Debug.Assert(ve.renderChainData.firstHeadCommand != null); + ve.renderChainData.lastHeadCommand = cmd.prev; + } + + + // Clean up renderChain Tail commands + if (ve.renderChainData.firstTailCommand == cmd) + { + //is this the last tailCommand? + if (ve.renderChainData.firstTailCommand == ve.renderChainData.lastTailCommand) + { + // Last command removed: extra checks + Debug.Assert(cmd.prev?.owner != ve || cmd.prev == ve.renderChainData.lastHeadCommand); + Debug.Assert(cmd.next?.owner != ve); + ve.renderChainData.firstTailCommand = null; + ve.renderChainData.lastTailCommand = null; + } + else + { + Debug.Assert(cmd.next.owner == ve); + Debug.Assert(ve.renderChainData.lastTailCommand != null); + ve.renderChainData.firstTailCommand = cmd.next; + } + } + else if (ve.renderChainData.lastTailCommand == cmd) + { + Debug.Assert(cmd.prev.owner == ve); + Debug.Assert(ve.renderChainData.firstTailCommand != null); + ve.renderChainData.lastTailCommand = cmd.prev; + } + renderChain.FreeCommand(cmd); + } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRElementBuilder.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRElementBuilder.cs index 359aeda61..4e287be6c 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRElementBuilder.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRElementBuilder.cs @@ -13,6 +13,7 @@ abstract class BaseElementBuilder public void Build(MeshGenerationContext mgc) { var ve = mgc.visualElement; + Debug.Assert(ve.areAncestorsAndSelfDisplayed); bool isGroupTransform = (ve.renderHints & RenderHints.GroupTransform) == RenderHints.GroupTransform; if (isGroupTransform) @@ -187,6 +188,7 @@ protected override void DrawVisualElementBackground(MeshGenerationContext mgc) { rectParams = MeshGenerator.RectangleParams.MakeSprite( ve.rect, + new Rect(0, 0, 1, 1), background.sprite, ScaleMode.StretchToFill, ve.panel.contextType, diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryProcessor.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryProcessor.cs index 4d04b26c7..8c02862c6 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryProcessor.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryProcessor.cs @@ -513,6 +513,7 @@ unsafe void ProcessMeshEntry(Entry entry, TextureId textureId) { // Set font atlas texture gradient scale cmd.state.sdfScale = entry.textScale; + cmd.state.sharpness = entry.fontSharpness; } m_VertsFilled += entry.vertices.Length; @@ -580,6 +581,7 @@ void AppendCommand(RenderChainCommand next) } else { + next.prev = m_LastCommand; m_LastCommand.next = next; m_LastCommand = next; } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryRecorder.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryRecorder.cs index ea2c3de3d..a5f033587 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryRecorder.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIREntryRecorder.cs @@ -44,6 +44,7 @@ class Entry public Texture texture; public float textScale; + public float fontSharpness; public VectorImage gradientsOwner; public Material material; public Action immediateCallback; @@ -106,7 +107,7 @@ public void DrawMesh(NativeSlice vertices, NativeSlice indices, AppendMeshEntry(entry); } - public void DrawSdfText(NativeSlice vertices, NativeSlice indices, Texture texture, float scale) + public void DrawSdfText(NativeSlice vertices, NativeSlice indices, Texture texture, float scale, float sharpness) { var entry = m_EntryPool.Get(); entry.type = EntryType.DrawSdfTextMesh; @@ -114,6 +115,7 @@ public void DrawSdfText(NativeSlice vertices, NativeSlice indice entry.indices = indices; entry.texture = texture; entry.textScale = scale; + entry.fontSharpness = sharpness; AppendMeshEntry(entry); } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRLayoutUpdater.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRLayoutUpdater.cs index b4355ac89..6abec9ffe 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRLayoutUpdater.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRLayoutUpdater.cs @@ -16,7 +16,6 @@ internal class UIRLayoutUpdater : BaseVisualTreeUpdater { const int kMaxValidateLayoutCount = 5; - private static readonly string s_Description = "Update Layout"; private static readonly ProfilerMarker s_ProfilerMarker = new ProfilerMarker(s_Description); public override ProfilerMarker profilerMarker => s_ProfilerMarker; @@ -33,7 +32,7 @@ public override void OnVersionChanged(VisualElement ve, VersionChangeType versio } } - List> changeEventsList = new List>(); + List<(Rect, Rect, VisualElement)> changeEventsList = new (); public override void Update() { @@ -52,7 +51,7 @@ public override void Update() visualTree.yogaNode.CalculateLayout(); panel.duringLayoutPhase = false; - UpdateSubTree(visualTree, true, changeEventsList); + UpdateSubTree(visualTree, changeEventsList); DispatchChangeEvents(changeEventsList, validateLayoutCount); if (validateLayoutCount++ >= kMaxValidateLayoutCount) @@ -70,8 +69,69 @@ public override void Update() visualTree.focusController.ReevaluateFocus(); } - private void UpdateSubTree(VisualElement ve, bool isDisplayed, List> changeEvents) + /// + /// This method update areAncestorsAndSelfDisplayed and disableRendering for the provided element and for all disabled element under. + /// It also trigger changeEvents if some element were disabled + /// This method does not update the elements under a displayed element since they are updated in UpdateSubTree + /// + /// + /// is a list to accumulate the event to dispatch later + /// false when any of the ancestors has display:none, otherwise true + /// has his state changed + static private bool UpdateHierarchyDisplayed(VisualElement ve, List<(Rect, Rect, VisualElement)> changeEvents, bool inheritedDisplayed = true) + { + var isDisplayed = inheritedDisplayed & ve.resolvedStyle.display != DisplayStyle.None; + + // If our parent is disabled, there is no need to disable the rendering on the child, + // but if the child is disabled, there is no need to re-enable it. + // If we are displayed we definitely don't want to be disabled! + if (inheritedDisplayed && !isDisplayed) + ve.disableRendering = true; + else if (isDisplayed) + ve.disableRendering = false; + + // If the status changed, we need to update the display status of the children. + // If the children has already the correct state, we don't need to continue + // (This check will stop the depth first branch from being updated) + if (ve.areAncestorsAndSelfDisplayed == isDisplayed) + return false; + + ve.areAncestorsAndSelfDisplayed = isDisplayed; + + // UpdateSubTree will propagate the Display:flex status but will not recurse in the case of a display:none + // We need recurse here to update/propagate the Display:none status and generate changeEvents when elements were hidden this frame + if (!isDisplayed) + { + if (inheritedDisplayed)//for the element having a displayed parent and that just became in display:none + { + // Make sure bounding box are re-computed (using yoga layout). + ve.IncrementVersion(VersionChangeType.Size); + } + + if ( ve.HasEventCallbacksOrDefaultActions(GeometryChangedEvent.EventCategory) ) + changeEvents.Add( (ve.lastLayout, Rect.zero, ve)); + + var childCount = ve.hierarchy.childCount; + for (int i = 0; i < childCount; ++i) + { + UpdateHierarchyDisplayed(ve.hierarchy[i], changeEvents, isDisplayed); + } + } + + return true; + } + + private void UpdateSubTree(VisualElement ve, List<(Rect, Rect, VisualElement)> changeEvents) { + // Because the UpdateSubTree recursion always starts form the root, and does not process subtrees that are not displayed, inheritedDisplay is always true. + bool isDisplayedJustChanged = UpdateHierarchyDisplayed(ve, changeEvents, true); + + // Events for disabled element and their descendant have been added by UpdateHierarchyDisplayed + // We don't trigger any IncrementVersion because we want to keep the UI buffered. + // the transform and size version change will occur when the element become displayed again. + if (!ve.areAncestorsAndSelfDisplayed) + return; + Rect yogaLayoutRect = new Rect(ve.yogaNode.LayoutX, ve.yogaNode.LayoutY, ve.yogaNode.LayoutWidth, ve.yogaNode.LayoutHeight); // we encode right/bottom into width/height @@ -89,7 +149,6 @@ private void UpdateSubTree(VisualElement ve, bool isDisplayed, List(lastLayoutRect, ve)); + changeEvents.Add((isDisplayedJustChanged ? Rect.zero : lastLayoutRect, yogaLayoutRect, ve)); } if (hasNewLayout) @@ -143,14 +204,11 @@ private void UpdateSubTree(VisualElement ve, bool isDisplayed, List> changeEvents, int currentLayoutPass) + private void DispatchChangeEvents(List<(Rect, Rect, VisualElement)> changeEvents, int currentLayoutPass) { - foreach (var changeElement in changeEvents) + foreach ((var oldRect, var newRect, var ve) in changeEvents) { - var ve = changeElement.Value; - - using (var evt = GeometryChangedEvent.GetPooled(changeElement.Key, ve.lastLayout)) + using (var evt = GeometryChangedEvent.GetPooled(oldRect, newRect)) { evt.layoutPass = currentLayoutPass; evt.elementTarget = ve; diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRMeshGenerator.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRMeshGenerator.cs index 6403b9205..108a77312 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRMeshGenerator.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRMeshGenerator.cs @@ -161,7 +161,8 @@ private static void AdjustUVsForScaleMode(Rect rect, Rect uv, Texture texture, S // Comparing aspects ratio is error-prone because the screenRect may end up being scaled by the // transform and the corners will end up being pixel aligned, possibly resulting in blurriness. - float srcAspect = (texture.width * uv.width) / (texture.height * uv.height); + // UUM-17136: uv width/height can be negative (e.g. when the UVs are flipped) + float srcAspect = Mathf.Abs((texture.width * uv.width) / (texture.height * uv.height)); float destAspect = rect.width / rect.height; switch (scaleMode) @@ -204,15 +205,15 @@ private static void AdjustUVsForScaleMode(Rect rect, Rect uv, Texture texture, S uvOut = uv; } - private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRect, Texture texture, Sprite sprite, ScaleMode scaleMode, out Rect rectOut, out Rect uvOut) + private static void AdjustSpriteUVsForScaleMode(Rect containerRect, Rect srcRect, Rect spriteGeomRect, Sprite sprite, ScaleMode scaleMode, out Rect rectOut, out Rect uvOut) { // Adjust the sprite rect size and then determine where the sprite geometry should be inside it. float srcAspect = sprite.rect.width / sprite.rect.height; - float destAspect = rect.width / rect.height; + float destAspect = containerRect.width / containerRect.height; // Normalize the geom rect for easy scaling - var geomRectNorm = geomRect; + var geomRectNorm = spriteGeomRect; geomRectNorm.position -= (Vector2)sprite.bounds.min; geomRectNorm.position /= sprite.bounds.size; geomRectNorm.size /= sprite.bounds.size; @@ -226,9 +227,9 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec { case ScaleMode.StretchToFill: { - var scale = rect.size; - rect.position = geomRectNorm.position * scale; - rect.size = geomRectNorm.size * scale; + var scale = containerRect.size; + containerRect.position = geomRectNorm.position * scale; + containerRect.size = geomRectNorm.size * scale; } break; @@ -240,16 +241,16 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec // - Compute the intersection of the geometry rect with the destination rect // - Re-evaluate the UVs from that intersection - var stretchedRect = rect; + var stretchedRect = containerRect; if (destAspect > srcAspect) { stretchedRect.height = stretchedRect.width / srcAspect; - stretchedRect.position = new Vector2(stretchedRect.position.x, -(stretchedRect.height - rect.height) / 2.0f); + stretchedRect.position = new Vector2(stretchedRect.position.x, -(stretchedRect.height - containerRect.height) / 2.0f); } else { stretchedRect.width = stretchedRect.height * srcAspect; - stretchedRect.position = new Vector2(-(stretchedRect.width - rect.width) / 2.0f, stretchedRect.position.y); + stretchedRect.position = new Vector2(-(stretchedRect.width - containerRect.width) / 2.0f, stretchedRect.position.y); } var scale = stretchedRect.size; @@ -257,7 +258,7 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec stretchedRect.size = geomRectNorm.size * scale; // Intersect the stretched rect with the destination rect to compute the new UVs - var newRect = RectIntersection(rect, stretchedRect); + var newRect = RectIntersection(containerRect, stretchedRect); if (newRect.width < UIRUtility.k_Epsilon || newRect.height < UIRUtility.k_Epsilon) newRect = Rect.zero; else @@ -272,11 +273,11 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec scalePos.y = 1.0f - uvScale.size.y - scalePos.y; uvScale.position = scalePos; - uv.position += uvScale.position * uv.size; - uv.size *= uvScale.size; + srcRect.position += uvScale.position * srcRect.size; + srcRect.size *= uvScale.size; } - rect = newRect; + containerRect = newRect; } break; @@ -285,16 +286,16 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec if (destAspect > srcAspect) { float stretch = srcAspect / destAspect; - rect = new Rect(rect.xMin + rect.width * (1.0f - stretch) * .5f, rect.yMin, stretch * rect.width, rect.height); + containerRect = new Rect(containerRect.xMin + containerRect.width * (1.0f - stretch) * .5f, containerRect.yMin, stretch * containerRect.width, containerRect.height); } else { float stretch = destAspect / srcAspect; - rect = new Rect(rect.xMin, rect.yMin + rect.height * (1.0f - stretch) * .5f, rect.width, stretch * rect.height); + containerRect = new Rect(containerRect.xMin, containerRect.yMin + containerRect.height * (1.0f - stretch) * .5f, containerRect.width, stretch * containerRect.height); } - rect.position += geomRectNorm.position * rect.size; - rect.size *= geomRectNorm.size; + containerRect.position += geomRectNorm.position * containerRect.size; + containerRect.size *= geomRectNorm.size; } break; @@ -303,8 +304,8 @@ private static void AdjustSpriteUVsForScaleMode(Rect rect, Rect uv, Rect geomRec } - rectOut = rect; - uvOut = uv; + rectOut = containerRect; + uvOut = srcRect; } internal static Rect RectIntersection(Rect a, Rect b) @@ -398,7 +399,7 @@ public static RectangleParams MakeTextured(Rect rect, Rect uv, Texture texture, return rp; } - public static RectangleParams MakeSprite(Rect rect, Sprite sprite, ScaleMode scaleMode, ContextType panelContext, bool hasRadius, ref Vector4 slices, bool useForRepeat = false) + public static RectangleParams MakeSprite(Rect containerRect, Rect subRect, Sprite sprite, ScaleMode scaleMode, ContextType panelContext, bool hasRadius, ref Vector4 slices, bool useForRepeat = false) { if (sprite == null || sprite.bounds.size.x < UIRUtility.k_Epsilon || sprite.bounds.size.y < UIRUtility.k_Epsilon) return new RectangleParams(); @@ -413,39 +414,54 @@ public static RectangleParams MakeSprite(Rect rect, Sprite sprite, ScaleMode sca ? UIElementsUtility.editorPlayModeTintColor : Color.white; - var geomRect = ComputeGeomRect(sprite); - var uv = ComputeUVRect(sprite); + var spriteGeomRect = ComputeGeomRect(sprite); // Min/Max Positions in the sprite + var spriteUVRect = ComputeUVRect(sprite); // Min/Max UVs in the sprite // Use a textured quad (ignoring tight-mesh) if dealing with slicing or with // scale-and-crop scale mode. This avoids expensive CPU-side transformation and // polygon clipping. var border = sprite.border; bool hasSlices = (border != Vector4.zero) || (slices != Vector4.zero); - bool useTexturedQuad = (scaleMode == ScaleMode.ScaleAndCrop) || hasSlices || hasRadius || useForRepeat; + bool hasSubRect = subRect != new Rect(0, 0, 1, 1); // In the future, we could implement flips with geometry flip + bool useTexturedQuad = (scaleMode == ScaleMode.ScaleAndCrop) || hasSlices || hasRadius || useForRepeat || hasSubRect; + // The sprite UVs are adjusted according to the rotation. But When we use a texture quad, we generate + // the UVs ourselves, so we need to apply the rotation to our rect. if (useTexturedQuad && sprite.packed && sprite.packingRotation != SpritePackingRotation.None) - uv = ApplyPackingRotation(uv, sprite.packingRotation); + spriteUVRect = ApplyPackingRotation(spriteUVRect, sprite.packingRotation); + + Rect srcRect; + if (hasSubRect) + { + // Remap the subRect within the sprite rect + srcRect = subRect; + srcRect.position *= spriteUVRect.size; + srcRect.position += spriteUVRect.position; + srcRect.size *= spriteUVRect.size; + } + else + srcRect = spriteUVRect; - AdjustSpriteUVsForScaleMode(rect, uv, geomRect, sprite.texture, sprite, scaleMode, out rect, out uv); + AdjustSpriteUVsForScaleMode(containerRect, srcRect, spriteGeomRect, sprite, scaleMode, out Rect adjustedDstRect, out Rect adjustedSrcRect); // Compute normalized subRect - var subRect = geomRect; - subRect.size /= (Vector2)sprite.bounds.size; - subRect.position -= (Vector2)sprite.bounds.min; - subRect.position /= (Vector2)sprite.bounds.size; - subRect.position = new Vector2(subRect.position.x, 1.0f - (subRect.position.y + subRect.height)); // Y-down for UIR + var normalizedRect = spriteGeomRect; + normalizedRect.size /= (Vector2)sprite.bounds.size; + normalizedRect.position -= (Vector2)sprite.bounds.min; + normalizedRect.position /= (Vector2)sprite.bounds.size; + normalizedRect.position = new Vector2(normalizedRect.position.x, 1.0f - (normalizedRect.position.y + normalizedRect.height)); // Y-down for UIR var rp = new RectangleParams { - rect = rect, - uv = uv, - subRect = subRect, + rect = adjustedDstRect, + uv = adjustedSrcRect, + subRect = normalizedRect, color = Color.white, - texture = useTexturedQuad ? sprite.texture : (Texture2D)null, - sprite = useTexturedQuad ? (Sprite)null : sprite, + texture = useTexturedQuad ? sprite.texture : null, + sprite = useTexturedQuad ? null : sprite, contentSize = sprite.rect.size, textureSize = new Vector2(sprite.texture.width, sprite.texture.height), - spriteGeomRect = geomRect, + spriteGeomRect = spriteGeomRect, scaleMode = scaleMode, playmodeTintColor = playmodeTintColor, meshFlags = sprite.packed ? MeshGenerationContext.MeshFlags.SkipDynamicAtlas : MeshGenerationContext.MeshFlags.None @@ -613,7 +629,7 @@ public void DrawText(TextInfo textInfo, Vector2 offset) DrawTextInfo(textInfo, offset, true); } - TextInfo m_TextInfo = new(); + TextInfo m_TextInfo = new TextInfo(VertexDataLayout.VBO); public void DrawText(string text, Vector2 pos, float fontSize, Color color, FontAsset font) { @@ -652,6 +668,7 @@ void DrawTextInfo(TextInfo textInfo, Vector2 offset, bool useHints) offset, false, false, + 0, 0); } else @@ -662,10 +679,17 @@ void DrawTextInfo(TextInfo textInfo, Vector2 offset, bool useHints) if (!TextGeneratorUtilities.IsBitmapRendering(textInfo.meshInfo[i].glyphRenderMode)) sdfScale = textInfo.meshInfo[i].material.GetFloat(TextShaderUtilities.ID_GradientScale); bool isDynamicColor = useHints && RenderEvents.NeedsColorID(currentElement); + var sharpness = textInfo.meshInfo[i].material.GetFloat("_Sharpness"); // Set the dynamic-color hint on TextCore fancy-text or the EditorUIE shader applies the // tint over the fragment output, affecting the outline/shadows. if (useHints) isDynamicColor = isDynamicColor || (sdfScale > 0 && RenderEvents.NeedsTextCoreSettings(currentElement)); + if (sharpness == 0.0f && currentElement.panel.contextType == ContextType.Editor) + { + var font = TextUtilities.GetFont(currentElement); + if (font) + sharpness = TextUtilities.getEditorTextSharpness(font.name); + } MakeText( textInfo.meshInfo[i].material.mainTexture, @@ -673,7 +697,8 @@ void DrawTextInfo(TextInfo textInfo, Vector2 offset, bool useHints) offset, true, isDynamicColor, - sdfScale); + sdfScale, + sharpness); } } } @@ -684,12 +709,12 @@ static Vertex ConvertTextVertexToUIRVertex(MeshInfo info, int index, Vector2 off { float dilate = 0.0f; // If Bold, dilate the shape (this value is hardcoded, should be set from the font actual bold weight) - if (info.uvs2[index].y < 0.0f) dilate = 1.0f; + if (info.vertexData[index].uv2.y < 0.0f) dilate = 1.0f; return new Vertex { - position = new Vector3(info.vertices[index].x + offset.x, info.vertices[index].y + offset.y, UIRUtility.k_MeshPosZ), - uv = new Vector2(info.uvs0[index].x, info.uvs0[index].y), - tint = info.colors32[index], + position = new Vector3(info.vertexData[index].position.x + offset.x, info.vertexData[index].position.y + offset.y, UIRUtility.k_MeshPosZ), + uv = new Vector2(info.vertexData[index].uv0.x, info.vertexData[index].uv0.y), + tint = info.vertexData[index].color, // TODO: Don't set the flags here. The mesh conversion should perform these changes flags = new Color32(0, (byte)(dilate * 255), 0, isDynamicColor ? (byte)1 : (byte)0) }; @@ -706,7 +731,7 @@ static int LimitTextVertices(int vertexCount, bool logTruncation = true) return s_MaxTextMeshVertices; } - void MakeText(Texture texture, MeshInfo meshInfo, Vector2 offset, bool isSdf, bool isDynamicColor, float sdfScale) + void MakeText(Texture texture, MeshInfo meshInfo, Vector2 offset, bool isSdf, bool isDynamicColor, float sdfScale, float sharpness) { int vertexCount = LimitTextVertices(meshInfo.vertexCount); int quadCount = vertexCount / 4; @@ -730,7 +755,7 @@ void MakeText(Texture texture, MeshInfo meshInfo, Vector2 offset, bool isSdf, bo } if (isSdf) - m_MeshGenerationContext.entryRecorder.DrawSdfText(vertices, indices, texture, sdfScale); + m_MeshGenerationContext.entryRecorder.DrawSdfText(vertices, indices, texture, sdfScale, sharpness); else m_MeshGenerationContext.entryRecorder.DrawMesh(vertices, indices, texture, true); } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRPainter2D.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRPainter2D.cs index da618dc70..3177ab59e 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRPainter2D.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRPainter2D.cs @@ -271,23 +271,7 @@ private bool ValidateState() return isValid; } - private static float s_MaxArcRadius = -1.0f; - private static float maxArcRadius - { - get { - if (s_MaxArcRadius < 0.0f) - { - if (!UIRenderDevice.vertexTexturingIsAvailable) - // If vertexTexturingIsAvailable is false, we probably are on a low-end - // device which may have fp16 fragment shader float precision. We limit - // the max arc radius even more in this case. - s_MaxArcRadius = 1.0e3f; - else - s_MaxArcRadius = 1.0e5f; - } - return s_MaxArcRadius; - } - } + private static float maxArcRadius => 1.0e5f; /// /// Begins a new path and empties the list of recorded sub-paths and resets the pen position to (0,0). diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs index 3d117bd97..4b7be2c54 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderChain.cs @@ -168,8 +168,6 @@ struct RenderNodeData public UIRenderDevice device; public Texture vectorAtlas, shaderInfoAtlas; public float dpiScale; - public NativeSlice transformConstants; - public NativeSlice clipRectConstants; }; RenderChainCommand m_FirstCommand; @@ -484,8 +482,7 @@ public void Render() //TODO: Reactivate this guard check once InspectorWindow is fixed to stop adding VEs during OnGUI //m_BlockDirtyRegistration = true; device.EvaluateChain(m_FirstCommand, standardMaterial, standardMaterial, vectorImageManager?.atlas, shaderInfoAllocator.atlas, - panel.scaledPixelsPerPoint, shaderInfoAllocator.transformConstants, shaderInfoAllocator.clipRectConstants, - m_RenderNodesData[0].matPropBlock, true, ref immediateException); + panel.scaledPixelsPerPoint, m_RenderNodesData[0].matPropBlock, true, ref immediateException); //m_BlockDirtyRegistration = false; } } @@ -633,6 +630,17 @@ public void UIEOnOpacityIdChanged(VisualElement ve) } } + public void UIEOnDisableRenderingChanged(VisualElement ve) + { + if (ve.renderChainData.isInChain) + { + if (m_BlockDirtyRegistration) + throw new InvalidOperationException("VisualElements cannot change their display style during generateVisualContent callback execution nor during visual tree rendering"); + + CommandManipulator.DisableElementRendering(this , ve, ve.disableRendering); + } + } + #endregion internal BaseVisualElementPanel panel { get; private set; } @@ -750,8 +758,7 @@ private unsafe static void OnRenderNodeExecute(IntPtr obj) Exception immediateException = null; rnd.device.EvaluateChain(rnd.firstCommand, rnd.initialMaterial, rnd.standardMaterial, rnd.vectorAtlas, rnd.shaderInfoAtlas, - rnd.dpiScale, rnd.transformConstants, rnd.clipRectConstants, - rnd.matPropBlock, false, ref immediateException); + rnd.dpiScale, rnd.matPropBlock, false, ref immediateException); } private static void OnRegisterIntermediateRenderers(Camera camera) @@ -773,8 +780,6 @@ private static void OnRegisterIntermediateRenderers(Camera camera) rndSource.vectorAtlas = renderChain.vectorImageManager?.atlas; rndSource.shaderInfoAtlas = renderChain.shaderInfoAllocator.atlas; rndSource.dpiScale = rtp.scaledPixelsPerPoint; - rndSource.transformConstants = renderChain.shaderInfoAllocator.transformConstants; - rndSource.clipRectConstants = renderChain.shaderInfoAllocator.clipRectConstants; if (renderChain.m_CustomMaterialCommands == 0) { @@ -925,7 +930,9 @@ void DrawStats() var drawStats = ((UIRenderDevice)device).GatherDrawStatistics(); GUI.Label(rc, "Frame index\t: " + drawStats.currentFrameIndex); rc.y += y_off; GUI.Label(rc, "Command count\t: " + drawStats.commandCount); rc.y += y_off; + GUI.Label(rc, "Skip cmd counts\t: " + drawStats.skippedCommandCount); rc.y += y_off; GUI.Label(rc, "Draw commands\t: " + drawStats.drawCommandCount); rc.y += y_off; + GUI.Label(rc, "Disable commands\t: " + drawStats.disableCommandCount); rc.y += y_off; GUI.Label(rc, "Draw ranges\t: " + drawStats.drawRangeCount); rc.y += y_off; GUI.Label(rc, "Draw range calls\t: " + drawStats.drawRangeCallCount); rc.y += y_off; GUI.Label(rc, "Material sets\t: " + drawStats.materialSetCount); rc.y += y_off; @@ -983,7 +990,6 @@ internal struct RenderChainVEData internal RenderChainCommand firstHeadCommand, lastHeadCommand; // Sequential for the same owner internal RenderChainCommand firstTailCommand, lastTailCommand; // Sequential for the same owner internal bool isInChain; - internal bool isHierarchyHidden; internal bool localFlipsWinding; internal bool localTransformScaleZero; internal bool worldFlipsWinding; @@ -1004,6 +1010,11 @@ internal struct RenderChainVEData internal RenderChainCommand lastTailOrHeadCommand { get { return lastTailCommand ?? lastHeadCommand; } } static internal bool AllocatesID(BMPAlloc alloc) { return (alloc.ownedState == OwnedState.Owned) && alloc.IsValid(); } static internal bool InheritsID(BMPAlloc alloc) { return (alloc.ownedState == OwnedState.Inherited) && alloc.IsValid(); } + + // This is set whenever there is repaint requested when HierarchyDisplayed == false and is used to trigger the repaint when it finally get displayed + internal bool pendingRepaint; + // This is set whenever a hierarchical repaint was needed when HierarchyDisplayed == false. + internal bool pendingHierarchicalRepaint; } struct TextureEntry diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderEvents.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderEvents.cs index bcd4e87d7..3245caa68 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderEvents.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRenderEvents.cs @@ -102,7 +102,7 @@ internal static uint DepthFirstOnChildAdded(RenderChain renderChain, VisualEleme ve.renderChainData.transformID = UIRVEShaderInfoAllocator.identityTransform; ve.renderChainData.clipRectID = UIRVEShaderInfoAllocator.infiniteClipRect; ve.renderChainData.opacityID = UIRVEShaderInfoAllocator.fullOpacity; - ve.renderChainData.colorID = BMPAlloc.Invalid; // Rely on vertex tint color by default + ve.renderChainData.colorID = BMPAlloc.Invalid; ve.renderChainData.backgroundColorID = BMPAlloc.Invalid; ve.renderChainData.borderLeftColorID = BMPAlloc.Invalid; ve.renderChainData.borderTopColorID = BMPAlloc.Invalid; @@ -654,7 +654,15 @@ public static bool UpdateTextCoreSettings(RenderChain renderChain, VisualElement ve.renderChainData.textCoreSettingsID = renderChain.shaderInfoAllocator.AllocTextCoreSettings(settings); if (RenderChainVEData.AllocatesID(ve.renderChainData.textCoreSettingsID)) + { + if (ve.panel.contextType == ContextType.Editor) + { + settings.faceColor *= UIElementsUtility.editorPlayModeTintColor; + settings.outlineColor *= UIElementsUtility.editorPlayModeTintColor; + settings.underlayColor *= UIElementsUtility.editorPlayModeTintColor; + } renderChain.shaderInfoAllocator.SetTextCoreSettingValue(ve.renderChainData.textCoreSettingsID, settings); + } return true; } @@ -722,12 +730,6 @@ static unsafe void PrepareNudgeVertices(VisualElement ve, UIRenderDevice device, dst = (IntPtr)newVerts.GetUnsafePtr(); count = vertCount; } - - public static bool IsElementHierarchyHidden(VisualElement ve) - { - return ve.resolvedStyle.display == DisplayStyle.None; - } - static VisualElement GetLastDeepestChild(VisualElement ve) { // O(n) of the visual tree depth, usually not too bad.. probably 10-15 in really bad cases diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRepaintUpdater.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRepaintUpdater.cs index db9263541..e8696c06e 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRepaintUpdater.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRRepaintUpdater.cs @@ -35,6 +35,8 @@ public override void OnVersionChanged(VisualElement ve, VersionChangeType versio bool borderRadiusChanged = (versionChangeType & VersionChangeType.BorderRadius) != 0; bool borderWidthChanged = (versionChangeType & VersionChangeType.BorderWidth) != 0; bool renderHintsChanged = (versionChangeType & VersionChangeType.RenderHints) != 0; + bool disableRenderingChanged = (versionChangeType & VersionChangeType.DisableRendering) != 0; + bool repaintChanged = (versionChangeType & VersionChangeType.Repaint) != 0; if (renderHintsChanged) renderChain.UIEOnRenderHintsChanged(ve); @@ -51,8 +53,11 @@ public override void OnVersionChanged(VisualElement ve, VersionChangeType versio if ((versionChangeType & VersionChangeType.Color) != 0) renderChain.UIEOnColorChanged(ve); - if ((versionChangeType & VersionChangeType.Repaint) != 0) + if (repaintChanged) renderChain.UIEOnVisualsChanged(ve, false); + + if (disableRenderingChanged && !repaintChanged) // The disable rendering will be taken care of by the repaint (it clear all commands) + renderChain.UIEOnDisableRenderingChanged(ve); } public override void Update() diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRTextureSlotManager.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRTextureSlotManager.cs index db413da3b..1a7466f7e 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRTextureSlotManager.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRTextureSlotManager.cs @@ -10,7 +10,7 @@ class TextureSlotManager { static TextureSlotManager() { - k_SlotCount = UIRenderDevice.shaderModelIs35 ? 8 : 4; + k_SlotCount = 8; slotIds = new int[k_SlotCount]; for (int i = 0; i < k_SlotCount; ++i) slotIds[i] = Shader.PropertyToID($"_Texture{i}"); @@ -47,7 +47,7 @@ public void Reset() { m_Textures[i] = TextureId.invalid; m_Tickets[i] = -1; - SetGpuData(i, TextureId.invalid, 1, 1, 0); + SetGpuData(i, TextureId.invalid, 1, 1, 0, 0); } } @@ -96,7 +96,7 @@ public int FindOldestSlot() return slot; } - public void Bind(TextureId id, float sdfScale, int slot, MaterialPropertyBlock mat) + public void Bind(TextureId id, float sdfScale, float sharpness, int slot, MaterialPropertyBlock mat) { Texture tex = textureRegistry.GetTexture(id); if (tex == null) // Case 1364578: Texture may have been destroyed @@ -104,18 +104,18 @@ public void Bind(TextureId id, float sdfScale, int slot, MaterialPropertyBlock m m_Textures[slot] = id; MarkUsed(slot); - SetGpuData(slot, id, tex.width, tex.height, sdfScale); + SetGpuData(slot, id, tex.width, tex.height, sdfScale, sharpness); mat.SetTexture(slotIds[slot], tex); mat.SetVectorArray(textureTableId, m_GpuTextures); } - public void SetGpuData(int slotIndex, TextureId id, int textureWidth, int textureHeight, float sdfScale) + public void SetGpuData(int slotIndex, TextureId id, int textureWidth, int textureHeight, float sdfScale, float sharpness) { int offset = slotIndex * k_SlotSize; float texelWidth = 1f / textureWidth; float texelHeight = 1f / textureHeight; m_GpuTextures[offset + 0] = new Vector4(id.ConvertToGpu(), texelWidth, texelHeight, sdfScale); - m_GpuTextures[offset + 1] = new Vector4(textureWidth, textureHeight, 0, 0); + m_GpuTextures[offset + 1] = new Vector4(textureWidth, textureHeight, sharpness, 0); } // Overridable for tests diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVEShaderInfoAllocator.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVEShaderInfoAllocator.cs index 75a3137aa..bcd15428a 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVEShaderInfoAllocator.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVEShaderInfoAllocator.cs @@ -157,11 +157,6 @@ internal struct UIRVEShaderInfoAllocator BitmapAllocator32 m_TransformAllocator, m_ClipRectAllocator, m_OpacityAllocator, m_ColorAllocator, m_TextSettingsAllocator; // All allocators take pages from the same storage bool m_StorageReallyCreated; - // Support for absence of vertex texturing - bool m_VertexTexturingEnabled; - NativeArray m_Transforms; - NativeArray m_ClipRects; - static int pageWidth { get { return BitmapAllocator32.kPageWidth; } } static int pageHeight { get { return 8; } } // 32*8 = 256, can be stored in a byte @@ -203,11 +198,6 @@ static Vector2Int AllocToTexelCoord(ref BitmapAllocator32 allocator, BMPAlloc al alloc.pageLine * allocator.entryHeight + y); } - static int AllocToConstantBufferIndex(BMPAlloc alloc) - { - return alloc.pageLine * pageWidth + alloc.bitIndex; - } - static bool AtlasRectMatchesPage(ref BitmapAllocator32 allocator, BMPAlloc defAlloc, RectInt atlasRect) { UInt16 x, y; @@ -217,15 +207,13 @@ static bool AtlasRectMatchesPage(ref BitmapAllocator32 allocator, BMPAlloc defAl (allocator.entryHeight * pageHeight == atlasRect.height); } - public NativeSlice transformConstants { get { return m_Transforms; } } - public NativeSlice clipRectConstants { get { return m_ClipRects; } } public Texture atlas { get { if (m_StorageReallyCreated) return m_Storage.texture; - return m_VertexTexturingEnabled ? UIRenderDevice.defaultShaderInfoTexFloat : UIRenderDevice.defaultShaderInfoTexARGB8; + return UIRenderDevice.defaultShaderInfoTexFloat; } } public bool internalAtlasCreated { get { return m_StorageReallyCreated; } } // For diagnostics really @@ -246,28 +234,12 @@ public void Construct() m_ColorAllocator.ForceFirstAlloc((ushort)clearColorTexel.x, (ushort)clearColorTexel.y); m_TextSettingsAllocator.Construct(pageHeight, 1, 4); m_TextSettingsAllocator.ForceFirstAlloc((ushort)defaultTextCoreSettingsTexel.x, (ushort)defaultTextCoreSettingsTexel.y); - - m_VertexTexturingEnabled = UIRenderDevice.vertexTexturingIsAvailable; - if (!m_VertexTexturingEnabled) - { - int constantCount = 20; // Once custom materials are exposed, this number must be parameterized - // Note that we can't do a lazy late allocation on the constants array size here (e.g. allocate only the default entry) - // because once the material receives an array parameter value for the first time, it clings to its size and never - // allows size changes afterwards without recreating the material, so we need to get the size right pessimistically - m_Transforms = new NativeArray(constantCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); - m_ClipRects = new NativeArray(constantCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); - m_Transforms[0] = new Transform3x4() { v0 = identityTransformRow0Value, v1 = identityTransformRow1Value, v2 = identityTransformRow2Value }; - m_ClipRects[0] = infiniteClipRectValue; - } } void ReallyCreateStorage() { // Because we want predictable placement of first pages, 64 will fit all default allocs - if (m_VertexTexturingEnabled) - m_Storage = new ShaderInfoStorageRGBAFloat(64); - else - m_Storage = new ShaderInfoStorageRGBA32(64); // If no vertex texturing, only store opacity in RGBA32 + m_Storage = new ShaderInfoStorageRGBAFloat(64); // The order of allocation from the atlas below is important. See the comment at the beginning of Construct(). RectInt rcTransform, rcClipRect, rcOpacity, rcColor, rcTextCoreSettings; @@ -292,16 +264,11 @@ void ReallyCreateStorage() if (!AtlasRectMatchesPage(ref m_TextSettingsAllocator, defaultTextCoreSettings, rcTextCoreSettings)) throw new Exception("Atlas text setting allocation failed unexpectedly"); - if (m_VertexTexturingEnabled) - { - SetTransformValue(identityTransform, identityTransformValue); - SetClipRectValue(infiniteClipRect, infiniteClipRectValue); - } - { - SetOpacityValue(fullOpacity, fullOpacityValue.w); - SetColorValue(clearColor, clearColorValue); - SetTextCoreSettingValue(defaultTextCoreSettings, defaultTextCoreSettingsValue); - } + SetTransformValue(identityTransform, identityTransformValue); + SetClipRectValue(infiniteClipRect, infiniteClipRectValue); + SetOpacityValue(fullOpacity, fullOpacityValue.w); + SetColorValue(clearColor, clearColorValue); + SetTextCoreSettingValue(defaultTextCoreSettings, defaultTextCoreSettingsValue); m_StorageReallyCreated = true; } @@ -311,10 +278,6 @@ public void Dispose() if (m_Storage != null) m_Storage.Dispose(); m_Storage = null; - if (m_ClipRects.IsCreated) - m_ClipRects.Dispose(); - if (m_Transforms.IsCreated) - m_Transforms.Dispose(); m_StorageReallyCreated = false; } @@ -328,16 +291,7 @@ public BMPAlloc AllocTransform() if (!m_StorageReallyCreated) ReallyCreateStorage(); - if (m_VertexTexturingEnabled) - return m_TransformAllocator.Allocate(m_Storage); - - var alloc = m_TransformAllocator.Allocate(null); // Don't want to allow new pages - - // If the returned alloc address fits within the constant buffer then succeed, otherwise fail - if (AllocToConstantBufferIndex(alloc) < m_Transforms.Length) - return alloc; - m_TransformAllocator.Free(alloc); // Not really necessary, but feels cleaner - return BMPAlloc.Invalid; + return m_TransformAllocator.Allocate(m_Storage); } public BMPAlloc AllocClipRect() @@ -345,16 +299,7 @@ public BMPAlloc AllocClipRect() if (!m_StorageReallyCreated) ReallyCreateStorage(); - if (m_VertexTexturingEnabled) - return m_ClipRectAllocator.Allocate(m_Storage); - - var alloc = m_ClipRectAllocator.Allocate(null); // Don't want to allow new pages - - // If the returned alloc address fits within the constant buffer then succeed, otherwise fail - if (AllocToConstantBufferIndex(alloc) < m_ClipRects.Length) - return alloc; - m_ClipRectAllocator.Free(alloc); // Not really necessary, but feels cleaner - return BMPAlloc.Invalid; + return m_ClipRectAllocator.Allocate(m_Storage); } public BMPAlloc AllocOpacity() @@ -384,31 +329,17 @@ public BMPAlloc AllocTextCoreSettings(TextCoreSettings settings) public void SetTransformValue(BMPAlloc alloc, Matrix4x4 xform) { Debug.Assert(alloc.IsValid()); - if (m_VertexTexturingEnabled) - { - var allocXY = AllocToTexelCoord(ref m_TransformAllocator, alloc); - m_Storage.SetTexel(allocXY.x, allocXY.y + 0, xform.GetRow(0)); - m_Storage.SetTexel(allocXY.x, allocXY.y + 1, xform.GetRow(1)); - m_Storage.SetTexel(allocXY.x, allocXY.y + 2, xform.GetRow(2)); - } - else - m_Transforms[AllocToConstantBufferIndex(alloc)] = new Transform3x4() - { - v0 = xform.GetRow(0), - v1 = xform.GetRow(1), - v2 = xform.GetRow(2) - }; + var allocXY = AllocToTexelCoord(ref m_TransformAllocator, alloc); + m_Storage.SetTexel(allocXY.x, allocXY.y + 0, xform.GetRow(0)); + m_Storage.SetTexel(allocXY.x, allocXY.y + 1, xform.GetRow(1)); + m_Storage.SetTexel(allocXY.x, allocXY.y + 2, xform.GetRow(2)); } public void SetClipRectValue(BMPAlloc alloc, Vector4 clipRect) { Debug.Assert(alloc.IsValid()); - if (m_VertexTexturingEnabled) - { - var allocXY = AllocToTexelCoord(ref m_ClipRectAllocator, alloc); - m_Storage.SetTexel(allocXY.x, allocXY.y, clipRect); - } - else m_ClipRects[AllocToConstantBufferIndex(alloc)] = clipRect; + var allocXY = AllocToTexelCoord(ref m_ClipRectAllocator, alloc); + m_Storage.SetTexel(allocXY.x, allocXY.y, clipRect); } public void SetOpacityValue(BMPAlloc alloc, float opacity) @@ -471,8 +402,7 @@ public Color32 TransformAllocToVertexData(BMPAlloc alloc) { Debug.Assert(pageWidth == 32 && pageHeight == 8); // Match the bit-shift values below for fast integer division UInt16 x = 0, y = 0; - if (m_VertexTexturingEnabled) - m_TransformAllocator.GetAllocPageAtlasLocation(alloc.page, out x, out y); + m_TransformAllocator.GetAllocPageAtlasLocation(alloc.page, out x, out y); return new Color32((byte)(x >> 5), (byte)(y >> 3), (byte)(alloc.pageLine * pageWidth + alloc.bitIndex), 0); } @@ -480,8 +410,7 @@ public Color32 ClipRectAllocToVertexData(BMPAlloc alloc) { Debug.Assert(pageWidth == 32 && pageHeight == 8); // Match the bit-shift values below for fast integer division UInt16 x = 0, y = 0; - if (m_VertexTexturingEnabled) - m_ClipRectAllocator.GetAllocPageAtlasLocation(alloc.page, out x, out y); + m_ClipRectAllocator.GetAllocPageAtlasLocation(alloc.page, out x, out y); return new Color32((byte)(x >> 5), (byte)(y >> 3), (byte)(alloc.pageLine * pageWidth + alloc.bitIndex), 0); } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVisualChangesProcessor.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVisualChangesProcessor.cs index 0870ade21..9083b2784 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVisualChangesProcessor.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRVisualChangesProcessor.cs @@ -53,17 +53,14 @@ public VisualChangesProcessor(RenderChain renderChain) public void ProcessOnVisualsChanged(VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { - bool hierarchical = (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.VisualsHierarchy) != 0; + bool hierarchical = ve.renderChainData.pendingHierarchicalRepaint || (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.VisualsHierarchy) != 0; if (hierarchical) stats.recursiveVisualUpdates++; else stats.nonRecursiveVisualUpdates++; - var parent = ve.hierarchy.parent; - var parentHierarchyHidden = parent != null && - (parent.renderChainData.isHierarchyHidden || RenderEvents.IsElementHierarchyHidden(parent)); - DepthFirstOnVisualsChanged(ve, dirtyID, parentHierarchyHidden, hierarchical, ref stats); + DepthFirstOnVisualsChanged(ve, dirtyID, hierarchical, ref stats); } - void DepthFirstOnVisualsChanged(VisualElement ve, uint dirtyID, bool parentHierarchyHidden, bool hierarchical, ref ChainBuilderStats stats) + void DepthFirstOnVisualsChanged(VisualElement ve, uint dirtyID, bool hierarchical, ref ChainBuilderStats stats) { if (dirtyID == ve.renderChainData.dirtyID) return; @@ -72,10 +69,17 @@ void DepthFirstOnVisualsChanged(VisualElement ve, uint dirtyID, bool parentHiera if (hierarchical) stats.recursiveVisualUpdatesExpanded++; - bool wasHierarchyHidden = ve.renderChainData.isHierarchyHidden; - ve.renderChainData.isHierarchyHidden = parentHierarchyHidden || RenderEvents.IsElementHierarchyHidden(ve); - if (wasHierarchyHidden != ve.renderChainData.isHierarchyHidden) - hierarchical = true; + if (!ve.areAncestorsAndSelfDisplayed) + { + if (hierarchical) + ve.renderChainData.pendingHierarchicalRepaint = true; + else + ve.renderChainData.pendingRepaint = true; + return; + } + + ve.renderChainData.pendingHierarchicalRepaint = false; + ve.renderChainData.pendingRepaint = false; if (!hierarchical && (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.AllVisuals) == RenderDataDirtyTypes.VisualsOpacityId) { @@ -105,21 +109,18 @@ void DepthFirstOnVisualsChanged(VisualElement ve, uint dirtyID, bool parentHiera rootEntry = rootEntry }); - if (!ve.renderChainData.isHierarchyHidden) - { - k_GenerateEntriesMarker.Begin(); - m_MeshGenerationContext.Begin(new MeshGenerationNode { placeholder = rootEntry }, ve); - m_ElementBuilder.Build(m_MeshGenerationContext); - m_MeshGenerationContext.End(); - k_GenerateEntriesMarker.End(); - } + k_GenerateEntriesMarker.Begin(); + m_MeshGenerationContext.Begin(new MeshGenerationNode { placeholder = rootEntry }, ve); + m_ElementBuilder.Build(m_MeshGenerationContext); + m_MeshGenerationContext.End(); + k_GenerateEntriesMarker.End(); if (hierarchical) { // Recurse on children int childrenCount = ve.hierarchy.childCount; for (int i = 0; i < childrenCount; i++) - DepthFirstOnVisualsChanged(ve.hierarchy[i], dirtyID, ve.renderChainData.isHierarchyHidden, true, ref stats); + DepthFirstOnVisualsChanged(ve.hierarchy[i], dirtyID, true, ref stats); } m_EntryProcessingList.Add(new EntryProcessingInfo diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderDevice.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderDevice.cs index 3e3967db8..c778e391c 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderDevice.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderDevice.cs @@ -99,8 +99,6 @@ public void Dispose() static readonly int s_GradientSettingsTexID = Shader.PropertyToID("_GradientSettingsTex"); static readonly int s_ShaderInfoTexID = Shader.PropertyToID("_ShaderInfoTex"); - static readonly int s_TransformsPropID = Shader.PropertyToID("_Transforms"); - static readonly int s_ClipRectsPropID = Shader.PropertyToID("_ClipRects"); static ProfilerMarker s_MarkerAllocate = new ProfilerMarker("UIR.Allocate"); static ProfilerMarker s_MarkerFree = new ProfilerMarker("UIR.Free"); @@ -108,14 +106,6 @@ public void Dispose() static ProfilerMarker s_MarkerFence = new ProfilerMarker("UIR.WaitOnFence"); static ProfilerMarker s_MarkerBeforeDraw = new ProfilerMarker("UIR.BeforeDraw"); - static bool? s_VertexTexturingIsAvailable; - const string k_VertexTexturingIsAvailableTag = "UIE_VertexTexturingIsAvailable"; - const string k_VertexTexturingIsAvailableTrue = "1"; - - static bool? s_ShaderModelIs35; - const string k_ShaderModelIs35Tag = "UIE_ShaderModelIs35"; - const string k_ShaderModelIs35True = "1"; - internal static uint maxVerticesPerPage => 0xFFFF; // On DX11, 0xFFFF is an invalid index (associated to primitive restart). With size = 0xFFFF last index is 0xFFFE cases:1259449 internal bool breakBatches { get; set; } @@ -161,7 +151,7 @@ protected UIRenderDevice(uint initialVertexCapacity, uint initialIndexCapacity, } #region Default system resources - static private Texture2D s_DefaultShaderInfoTexFloat, s_DefaultShaderInfoTexARGB8; + static private Texture2D s_DefaultShaderInfoTexFloat; static internal Texture2D defaultShaderInfoTexFloat { get @@ -186,63 +176,8 @@ static internal Texture2D defaultShaderInfoTexFloat return s_DefaultShaderInfoTexFloat; } } - static internal Texture2D defaultShaderInfoTexARGB8 - { - get - { - if (s_DefaultShaderInfoTexARGB8 == null) - { - s_DefaultShaderInfoTexARGB8 = new Texture2D(64, 64, TextureFormat.RGBA32, false); // No mips - s_DefaultShaderInfoTexARGB8.name = "DefaultShaderInfoTexARGB8"; - s_DefaultShaderInfoTexARGB8.hideFlags = HideFlags.HideAndDontSave; - s_DefaultShaderInfoTexARGB8.filterMode = FilterMode.Point; - s_DefaultShaderInfoTexARGB8.SetPixel(UIRVEShaderInfoAllocator.fullOpacityTexel.x, UIRVEShaderInfoAllocator.fullOpacityTexel.y, UIRVEShaderInfoAllocator.fullOpacityValue); - s_DefaultShaderInfoTexARGB8.SetPixel(UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.x, UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.y + 0, Color.white); - s_DefaultShaderInfoTexARGB8.SetPixel(UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.x, UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.y + 1, Color.clear); - s_DefaultShaderInfoTexARGB8.SetPixel(UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.x, UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.y + 2, Color.clear); - s_DefaultShaderInfoTexARGB8.SetPixel(UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.x, UIRVEShaderInfoAllocator.defaultTextCoreSettingsTexel.y + 3, Color.clear); - s_DefaultShaderInfoTexARGB8.Apply(false, true); - } - return s_DefaultShaderInfoTexARGB8; - } - } - #endregion - - static internal bool vertexTexturingIsAvailable - { - get - { - if (!s_VertexTexturingIsAvailable.HasValue) - { - var stockDefaultShader = Shader.Find(UIRUtility.k_DefaultShaderName); - var stockDefaultMaterial = new Material(stockDefaultShader); - stockDefaultMaterial.hideFlags |= HideFlags.DontSaveInEditor; - string tagValue = stockDefaultMaterial.GetTag(k_VertexTexturingIsAvailableTag, false); - UIRUtility.Destroy(stockDefaultMaterial); - s_VertexTexturingIsAvailable = (tagValue == k_VertexTexturingIsAvailableTrue); - } - - return s_VertexTexturingIsAvailable.Value; - } - } - - internal static bool shaderModelIs35 - { - get - { - if (!s_ShaderModelIs35.HasValue) - { - var stockDefaultShader = Shader.Find(UIRUtility.k_DefaultShaderName); - var stockDefaultMaterial = new Material(stockDefaultShader); - stockDefaultMaterial.hideFlags |= HideFlags.DontSaveInEditor; - string tagValue = stockDefaultMaterial.GetTag(k_ShaderModelIs35Tag, false); - UIRUtility.Destroy(stockDefaultMaterial); - s_ShaderModelIs35 = (tagValue == k_ShaderModelIs35True); - } - return s_ShaderModelIs35.Value; - } - } + #endregion void InitVertexDeclaration() { @@ -711,7 +646,7 @@ void ApplyDrawCommandState(RenderChainCommand cmd, int textureSlot, Material new if (textureSlot < 0) { textureSlot = m_TextureSlotManager.FindOldestSlot(); - m_TextureSlotManager.Bind(cmd.state.texture, cmd.state.sdfScale, textureSlot, st.stateMatProps); + m_TextureSlotManager.Bind(cmd.state.texture, cmd.state.sdfScale, cmd.state.sharpness, textureSlot, st.stateMatProps); st.mustApplyStateBlock = true; } else @@ -772,7 +707,7 @@ void ApplyBatchState(ref EvaluationState st, bool allowMaterialChange) } public unsafe void EvaluateChain(RenderChainCommand head, Material initialMat, Material defaultMat, Texture gradientSettings, Texture shaderInfo, - float pixelsPerPoint, NativeSlice transforms, NativeSlice clipRects, MaterialPropertyBlock stateMatProps, bool allowMaterialChange, + float pixelsPerPoint, MaterialPropertyBlock stateMatProps, bool allowMaterialChange, ref Exception immediateException) { Utility.ProfileDrawChainBegin(); @@ -791,10 +726,6 @@ public unsafe void EvaluateChain(RenderChainCommand head, Material initialMat, M m_StandardMatProps.SetTexture(s_GradientSettingsTexID, gradientSettings); if (shaderInfo != null) m_StandardMatProps.SetTexture(s_ShaderInfoTexID, shaderInfo); - if (transforms.Length > 0) - UIR.Utility.SetVectorArray(m_StandardMatProps, s_TransformsPropID, transforms); - if (clipRects.Length > 0) - UIR.Utility.SetVectorArray(m_StandardMatProps, s_ClipRectsPropID, clipRects); Utility.SetPropertyBlock(m_StandardMatProps); } @@ -805,6 +736,7 @@ public unsafe void EvaluateChain(RenderChainCommand head, Material initialMat, M int rangesReady = 0; DrawBufferRange curDrawRange = new DrawBufferRange(); int curDrawIndex = -1; + int disableCounter = 0; var st = new EvaluationState { @@ -818,7 +750,30 @@ public unsafe void EvaluateChain(RenderChainCommand head, Material initialMat, M while (head != null) { - m_DrawStats.commandCount++; + + if (head.type == CommandType.BeginDisable) + { + m_DrawStats.commandCount++; + disableCounter++; + head = head.next; + continue; + } + + if (head.type == CommandType.EndDisable) + { + m_DrawStats.commandCount++; + disableCounter--; + head = head.next; + continue; + } + + if (disableCounter > 0) + { + m_DrawStats.skippedCommandCount++; + head = head.next; + continue; + } + m_DrawStats.drawCommandCount += (head.type == CommandType.Draw ? 1u : 0u); bool isLastRange = curDrawRange.indexCount > 0 && rangesReady == rangesCount - 1; @@ -990,6 +945,8 @@ public unsafe void EvaluateChain(RenderChainCommand head, Material initialMat, M KickRanges(ranges, ref rangesReady, ref rangesStart, rangesCount, st.curPage); } + Debug.Assert(disableCounter == 0, "Rendering disabled counter is not 0, indicating a mismatch of commands"); + UpdateFenceValue(); Utility.ProfileDrawChainEnd(); @@ -1191,11 +1148,6 @@ internal static void PrepareForGfxDeviceRecreate() UIRUtility.Destroy(s_DefaultShaderInfoTexFloat); s_DefaultShaderInfoTexFloat = null; } - if (s_DefaultShaderInfoTexARGB8 != null) - { - UIRUtility.Destroy(s_DefaultShaderInfoTexARGB8); - s_DefaultShaderInfoTexARGB8 = null; - } } internal static void WrapUpGfxDeviceRecreate() { m_ActiveDeviceCount -= 1; } @@ -1244,7 +1196,9 @@ internal struct DrawStatistics public int currentFrameIndex; public uint totalIndices; public uint commandCount; + public uint skippedCommandCount; public uint drawCommandCount; + public uint disableCommandCount; public uint materialSetCount; public uint drawRangeCount; public uint drawRangeCallCount; @@ -1283,11 +1237,6 @@ private static void ProcessDeviceFreeQueue() UIRUtility.Destroy(s_DefaultShaderInfoTexFloat); s_DefaultShaderInfoTexFloat = null; } - if (s_DefaultShaderInfoTexARGB8 != null) - { - UIRUtility.Destroy(s_DefaultShaderInfoTexARGB8); - s_DefaultShaderInfoTexARGB8 = null; - } Utility.NotifyOfUIREvents(false); m_SubscribedToNotifications = false; } diff --git a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderers.cs b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderers.cs index de8b9602f..db7460251 100644 --- a/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderers.cs +++ b/ModuleOverrides/com.unity.ui/Core/Renderer/UIRenderer/UIRenderers.cs @@ -31,6 +31,7 @@ internal struct State public TextureId texture; public int stencilRef; public float sdfScale; + public float sharpness; } internal enum CommandType @@ -42,6 +43,7 @@ internal enum CommandType PushRenderTexture, PopRenderTexture, BlitToPreviousRT, //From Active target to previous on RT stack PushDefaultMaterial, PopDefaultMaterial, + BeginDisable, EndDisable, } internal class DrawParams diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs index aa70edf1c..008198ae1 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs @@ -1234,7 +1234,7 @@ public void ApplyPropertyAnimation(VisualElement ve, StylePropertyId id, int new { layoutData.Write().display = (DisplayStyle)newValue; ve.yogaNode.Display = (YogaDisplay)newValue; - ve.IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint); + ve.IncrementVersion(VersionChangeType.Layout); } break; diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs index 0a5f4d828..6b146f97a 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs @@ -391,7 +391,7 @@ StyleEnum IStyle.display { if (SetStyleValue(StylePropertyId.Display, value)) { - ve.IncrementVersion(VersionChangeType.Styles | VersionChangeType.Layout | VersionChangeType.Repaint); + ve.IncrementVersion(VersionChangeType.Styles | VersionChangeType.Layout); ve.yogaNode.Display = (YogaDisplay)ve.computedStyle.display; } } diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/ResolvedStyleProperties.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ResolvedStyleProperties.cs new file mode 100644 index 000000000..88204a8a7 --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ResolvedStyleProperties.cs @@ -0,0 +1,97 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +/******************************************************************************/ +// +// DO NOT MODIFY +// This file has been generated by the UIElementsGenerator tool +// See VisualElementResolvedStylePropertiesCsGenerator class for details +// +/******************************************************************************/ +namespace UnityEngine.UIElements +{ + public partial class VisualElement + { + static partial class ResolvedStyleProperties + { + internal static readonly DataBindingProperty alignContentProperty = "resolvedStyle." + nameof(IResolvedStyle.alignContent); + internal static readonly DataBindingProperty alignItemsProperty = "resolvedStyle." + nameof(IResolvedStyle.alignItems); + internal static readonly DataBindingProperty alignSelfProperty = "resolvedStyle." + nameof(IResolvedStyle.alignSelf); + internal static readonly DataBindingProperty backgroundColorProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundColor); + internal static readonly DataBindingProperty backgroundImageProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundImage); + internal static readonly DataBindingProperty backgroundPositionXProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundPositionX); + internal static readonly DataBindingProperty backgroundPositionYProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundPositionY); + internal static readonly DataBindingProperty backgroundRepeatProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundRepeat); + internal static readonly DataBindingProperty backgroundSizeProperty = "resolvedStyle." + nameof(IResolvedStyle.backgroundSize); + internal static readonly DataBindingProperty borderBottomColorProperty = "resolvedStyle." + nameof(IResolvedStyle.borderBottomColor); + internal static readonly DataBindingProperty borderBottomLeftRadiusProperty = "resolvedStyle." + nameof(IResolvedStyle.borderBottomLeftRadius); + internal static readonly DataBindingProperty borderBottomRightRadiusProperty = "resolvedStyle." + nameof(IResolvedStyle.borderBottomRightRadius); + internal static readonly DataBindingProperty borderBottomWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.borderBottomWidth); + internal static readonly DataBindingProperty borderLeftColorProperty = "resolvedStyle." + nameof(IResolvedStyle.borderLeftColor); + internal static readonly DataBindingProperty borderLeftWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.borderLeftWidth); + internal static readonly DataBindingProperty borderRightColorProperty = "resolvedStyle." + nameof(IResolvedStyle.borderRightColor); + internal static readonly DataBindingProperty borderRightWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.borderRightWidth); + internal static readonly DataBindingProperty borderTopColorProperty = "resolvedStyle." + nameof(IResolvedStyle.borderTopColor); + internal static readonly DataBindingProperty borderTopLeftRadiusProperty = "resolvedStyle." + nameof(IResolvedStyle.borderTopLeftRadius); + internal static readonly DataBindingProperty borderTopRightRadiusProperty = "resolvedStyle." + nameof(IResolvedStyle.borderTopRightRadius); + internal static readonly DataBindingProperty borderTopWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.borderTopWidth); + internal static readonly DataBindingProperty bottomProperty = "resolvedStyle." + nameof(IResolvedStyle.bottom); + internal static readonly DataBindingProperty colorProperty = "resolvedStyle." + nameof(IResolvedStyle.color); + internal static readonly DataBindingProperty displayProperty = "resolvedStyle." + nameof(IResolvedStyle.display); + internal static readonly DataBindingProperty flexBasisProperty = "resolvedStyle." + nameof(IResolvedStyle.flexBasis); + internal static readonly DataBindingProperty flexDirectionProperty = "resolvedStyle." + nameof(IResolvedStyle.flexDirection); + internal static readonly DataBindingProperty flexGrowProperty = "resolvedStyle." + nameof(IResolvedStyle.flexGrow); + internal static readonly DataBindingProperty flexShrinkProperty = "resolvedStyle." + nameof(IResolvedStyle.flexShrink); + internal static readonly DataBindingProperty flexWrapProperty = "resolvedStyle." + nameof(IResolvedStyle.flexWrap); + internal static readonly DataBindingProperty fontSizeProperty = "resolvedStyle." + nameof(IResolvedStyle.fontSize); + internal static readonly DataBindingProperty heightProperty = "resolvedStyle." + nameof(IResolvedStyle.height); + internal static readonly DataBindingProperty justifyContentProperty = "resolvedStyle." + nameof(IResolvedStyle.justifyContent); + internal static readonly DataBindingProperty leftProperty = "resolvedStyle." + nameof(IResolvedStyle.left); + internal static readonly DataBindingProperty letterSpacingProperty = "resolvedStyle." + nameof(IResolvedStyle.letterSpacing); + internal static readonly DataBindingProperty marginBottomProperty = "resolvedStyle." + nameof(IResolvedStyle.marginBottom); + internal static readonly DataBindingProperty marginLeftProperty = "resolvedStyle." + nameof(IResolvedStyle.marginLeft); + internal static readonly DataBindingProperty marginRightProperty = "resolvedStyle." + nameof(IResolvedStyle.marginRight); + internal static readonly DataBindingProperty marginTopProperty = "resolvedStyle." + nameof(IResolvedStyle.marginTop); + internal static readonly DataBindingProperty maxHeightProperty = "resolvedStyle." + nameof(IResolvedStyle.maxHeight); + internal static readonly DataBindingProperty maxWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.maxWidth); + internal static readonly DataBindingProperty minHeightProperty = "resolvedStyle." + nameof(IResolvedStyle.minHeight); + internal static readonly DataBindingProperty minWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.minWidth); + internal static readonly DataBindingProperty opacityProperty = "resolvedStyle." + nameof(IResolvedStyle.opacity); + internal static readonly DataBindingProperty paddingBottomProperty = "resolvedStyle." + nameof(IResolvedStyle.paddingBottom); + internal static readonly DataBindingProperty paddingLeftProperty = "resolvedStyle." + nameof(IResolvedStyle.paddingLeft); + internal static readonly DataBindingProperty paddingRightProperty = "resolvedStyle." + nameof(IResolvedStyle.paddingRight); + internal static readonly DataBindingProperty paddingTopProperty = "resolvedStyle." + nameof(IResolvedStyle.paddingTop); + internal static readonly DataBindingProperty positionProperty = "resolvedStyle." + nameof(IResolvedStyle.position); + internal static readonly DataBindingProperty rightProperty = "resolvedStyle." + nameof(IResolvedStyle.right); + internal static readonly DataBindingProperty rotateProperty = "resolvedStyle." + nameof(IResolvedStyle.rotate); + internal static readonly DataBindingProperty scaleProperty = "resolvedStyle." + nameof(IResolvedStyle.scale); + internal static readonly DataBindingProperty textOverflowProperty = "resolvedStyle." + nameof(IResolvedStyle.textOverflow); + internal static readonly DataBindingProperty topProperty = "resolvedStyle." + nameof(IResolvedStyle.top); + internal static readonly DataBindingProperty transformOriginProperty = "resolvedStyle." + nameof(IResolvedStyle.transformOrigin); + internal static readonly DataBindingProperty transitionDelayProperty = "resolvedStyle." + nameof(IResolvedStyle.transitionDelay); + internal static readonly DataBindingProperty transitionDurationProperty = "resolvedStyle." + nameof(IResolvedStyle.transitionDuration); + internal static readonly DataBindingProperty transitionPropertyProperty = "resolvedStyle." + nameof(IResolvedStyle.transitionProperty); + internal static readonly DataBindingProperty transitionTimingFunctionProperty = "resolvedStyle." + nameof(IResolvedStyle.transitionTimingFunction); + internal static readonly DataBindingProperty translateProperty = "resolvedStyle." + nameof(IResolvedStyle.translate); + internal static readonly DataBindingProperty unityBackgroundImageTintColorProperty = "resolvedStyle." + nameof(IResolvedStyle.unityBackgroundImageTintColor); + internal static readonly DataBindingProperty unityFontProperty = "resolvedStyle." + nameof(IResolvedStyle.unityFont); + internal static readonly DataBindingProperty unityFontDefinitionProperty = "resolvedStyle." + nameof(IResolvedStyle.unityFontDefinition); + internal static readonly DataBindingProperty unityFontStyleAndWeightProperty = "resolvedStyle." + nameof(IResolvedStyle.unityFontStyleAndWeight); + internal static readonly DataBindingProperty unityParagraphSpacingProperty = "resolvedStyle." + nameof(IResolvedStyle.unityParagraphSpacing); + internal static readonly DataBindingProperty unitySliceBottomProperty = "resolvedStyle." + nameof(IResolvedStyle.unitySliceBottom); + internal static readonly DataBindingProperty unitySliceLeftProperty = "resolvedStyle." + nameof(IResolvedStyle.unitySliceLeft); + internal static readonly DataBindingProperty unitySliceRightProperty = "resolvedStyle." + nameof(IResolvedStyle.unitySliceRight); + internal static readonly DataBindingProperty unitySliceScaleProperty = "resolvedStyle." + nameof(IResolvedStyle.unitySliceScale); + internal static readonly DataBindingProperty unitySliceTopProperty = "resolvedStyle." + nameof(IResolvedStyle.unitySliceTop); + internal static readonly DataBindingProperty unityTextAlignProperty = "resolvedStyle." + nameof(IResolvedStyle.unityTextAlign); + internal static readonly DataBindingProperty unityTextOutlineColorProperty = "resolvedStyle." + nameof(IResolvedStyle.unityTextOutlineColor); + internal static readonly DataBindingProperty unityTextOutlineWidthProperty = "resolvedStyle." + nameof(IResolvedStyle.unityTextOutlineWidth); + internal static readonly DataBindingProperty unityTextOverflowPositionProperty = "resolvedStyle." + nameof(IResolvedStyle.unityTextOverflowPosition); + internal static readonly DataBindingProperty visibilityProperty = "resolvedStyle." + nameof(IResolvedStyle.visibility); + internal static readonly DataBindingProperty whiteSpaceProperty = "resolvedStyle." + nameof(IResolvedStyle.whiteSpace); + internal static readonly DataBindingProperty widthProperty = "resolvedStyle." + nameof(IResolvedStyle.width); + internal static readonly DataBindingProperty wordSpacingProperty = "resolvedStyle." + nameof(IResolvedStyle.wordSpacing); + } + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/StyleProperties.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/StyleProperties.cs new file mode 100644 index 000000000..fe5632a9f --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/StyleProperties.cs @@ -0,0 +1,101 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +/******************************************************************************/ +// +// DO NOT MODIFY +// This file has been generated by the UIElementsGenerator tool +// See VisualElementStylePropertiesCsGenerator class for details +// +/******************************************************************************/ +namespace UnityEngine.UIElements +{ + public partial class VisualElement + { + static partial class StyleProperties + { + internal static readonly DataBindingProperty alignContentProperty = "style." + nameof(IStyle.alignContent); + internal static readonly DataBindingProperty alignItemsProperty = "style." + nameof(IStyle.alignItems); + internal static readonly DataBindingProperty alignSelfProperty = "style." + nameof(IStyle.alignSelf); + internal static readonly DataBindingProperty backgroundColorProperty = "style." + nameof(IStyle.backgroundColor); + internal static readonly DataBindingProperty backgroundImageProperty = "style." + nameof(IStyle.backgroundImage); + internal static readonly DataBindingProperty backgroundPositionXProperty = "style." + nameof(IStyle.backgroundPositionX); + internal static readonly DataBindingProperty backgroundPositionYProperty = "style." + nameof(IStyle.backgroundPositionY); + internal static readonly DataBindingProperty backgroundRepeatProperty = "style." + nameof(IStyle.backgroundRepeat); + internal static readonly DataBindingProperty backgroundSizeProperty = "style." + nameof(IStyle.backgroundSize); + internal static readonly DataBindingProperty borderBottomColorProperty = "style." + nameof(IStyle.borderBottomColor); + internal static readonly DataBindingProperty borderBottomLeftRadiusProperty = "style." + nameof(IStyle.borderBottomLeftRadius); + internal static readonly DataBindingProperty borderBottomRightRadiusProperty = "style." + nameof(IStyle.borderBottomRightRadius); + internal static readonly DataBindingProperty borderBottomWidthProperty = "style." + nameof(IStyle.borderBottomWidth); + internal static readonly DataBindingProperty borderLeftColorProperty = "style." + nameof(IStyle.borderLeftColor); + internal static readonly DataBindingProperty borderLeftWidthProperty = "style." + nameof(IStyle.borderLeftWidth); + internal static readonly DataBindingProperty borderRightColorProperty = "style." + nameof(IStyle.borderRightColor); + internal static readonly DataBindingProperty borderRightWidthProperty = "style." + nameof(IStyle.borderRightWidth); + internal static readonly DataBindingProperty borderTopColorProperty = "style." + nameof(IStyle.borderTopColor); + internal static readonly DataBindingProperty borderTopLeftRadiusProperty = "style." + nameof(IStyle.borderTopLeftRadius); + internal static readonly DataBindingProperty borderTopRightRadiusProperty = "style." + nameof(IStyle.borderTopRightRadius); + internal static readonly DataBindingProperty borderTopWidthProperty = "style." + nameof(IStyle.borderTopWidth); + internal static readonly DataBindingProperty bottomProperty = "style." + nameof(IStyle.bottom); + internal static readonly DataBindingProperty colorProperty = "style." + nameof(IStyle.color); + internal static readonly DataBindingProperty cursorProperty = "style." + nameof(IStyle.cursor); + internal static readonly DataBindingProperty displayProperty = "style." + nameof(IStyle.display); + internal static readonly DataBindingProperty flexBasisProperty = "style." + nameof(IStyle.flexBasis); + internal static readonly DataBindingProperty flexDirectionProperty = "style." + nameof(IStyle.flexDirection); + internal static readonly DataBindingProperty flexGrowProperty = "style." + nameof(IStyle.flexGrow); + internal static readonly DataBindingProperty flexShrinkProperty = "style." + nameof(IStyle.flexShrink); + internal static readonly DataBindingProperty flexWrapProperty = "style." + nameof(IStyle.flexWrap); + internal static readonly DataBindingProperty fontSizeProperty = "style." + nameof(IStyle.fontSize); + internal static readonly DataBindingProperty heightProperty = "style." + nameof(IStyle.height); + internal static readonly DataBindingProperty justifyContentProperty = "style." + nameof(IStyle.justifyContent); + internal static readonly DataBindingProperty leftProperty = "style." + nameof(IStyle.left); + internal static readonly DataBindingProperty letterSpacingProperty = "style." + nameof(IStyle.letterSpacing); + internal static readonly DataBindingProperty marginBottomProperty = "style." + nameof(IStyle.marginBottom); + internal static readonly DataBindingProperty marginLeftProperty = "style." + nameof(IStyle.marginLeft); + internal static readonly DataBindingProperty marginRightProperty = "style." + nameof(IStyle.marginRight); + internal static readonly DataBindingProperty marginTopProperty = "style." + nameof(IStyle.marginTop); + internal static readonly DataBindingProperty maxHeightProperty = "style." + nameof(IStyle.maxHeight); + internal static readonly DataBindingProperty maxWidthProperty = "style." + nameof(IStyle.maxWidth); + internal static readonly DataBindingProperty minHeightProperty = "style." + nameof(IStyle.minHeight); + internal static readonly DataBindingProperty minWidthProperty = "style." + nameof(IStyle.minWidth); + internal static readonly DataBindingProperty opacityProperty = "style." + nameof(IStyle.opacity); + internal static readonly DataBindingProperty overflowProperty = "style." + nameof(IStyle.overflow); + internal static readonly DataBindingProperty paddingBottomProperty = "style." + nameof(IStyle.paddingBottom); + internal static readonly DataBindingProperty paddingLeftProperty = "style." + nameof(IStyle.paddingLeft); + internal static readonly DataBindingProperty paddingRightProperty = "style." + nameof(IStyle.paddingRight); + internal static readonly DataBindingProperty paddingTopProperty = "style." + nameof(IStyle.paddingTop); + internal static readonly DataBindingProperty positionProperty = "style." + nameof(IStyle.position); + internal static readonly DataBindingProperty rightProperty = "style." + nameof(IStyle.right); + internal static readonly DataBindingProperty rotateProperty = "style." + nameof(IStyle.rotate); + internal static readonly DataBindingProperty scaleProperty = "style." + nameof(IStyle.scale); + internal static readonly DataBindingProperty textOverflowProperty = "style." + nameof(IStyle.textOverflow); + internal static readonly DataBindingProperty textShadowProperty = "style." + nameof(IStyle.textShadow); + internal static readonly DataBindingProperty topProperty = "style." + nameof(IStyle.top); + internal static readonly DataBindingProperty transformOriginProperty = "style." + nameof(IStyle.transformOrigin); + internal static readonly DataBindingProperty transitionDelayProperty = "style." + nameof(IStyle.transitionDelay); + internal static readonly DataBindingProperty transitionDurationProperty = "style." + nameof(IStyle.transitionDuration); + internal static readonly DataBindingProperty transitionPropertyProperty = "style." + nameof(IStyle.transitionProperty); + internal static readonly DataBindingProperty transitionTimingFunctionProperty = "style." + nameof(IStyle.transitionTimingFunction); + internal static readonly DataBindingProperty translateProperty = "style." + nameof(IStyle.translate); + internal static readonly DataBindingProperty unityBackgroundImageTintColorProperty = "style." + nameof(IStyle.unityBackgroundImageTintColor); + internal static readonly DataBindingProperty unityFontProperty = "style." + nameof(IStyle.unityFont); + internal static readonly DataBindingProperty unityFontDefinitionProperty = "style." + nameof(IStyle.unityFontDefinition); + internal static readonly DataBindingProperty unityFontStyleAndWeightProperty = "style." + nameof(IStyle.unityFontStyleAndWeight); + internal static readonly DataBindingProperty unityOverflowClipBoxProperty = "style." + nameof(IStyle.unityOverflowClipBox); + internal static readonly DataBindingProperty unityParagraphSpacingProperty = "style." + nameof(IStyle.unityParagraphSpacing); + internal static readonly DataBindingProperty unitySliceBottomProperty = "style." + nameof(IStyle.unitySliceBottom); + internal static readonly DataBindingProperty unitySliceLeftProperty = "style." + nameof(IStyle.unitySliceLeft); + internal static readonly DataBindingProperty unitySliceRightProperty = "style." + nameof(IStyle.unitySliceRight); + internal static readonly DataBindingProperty unitySliceScaleProperty = "style." + nameof(IStyle.unitySliceScale); + internal static readonly DataBindingProperty unitySliceTopProperty = "style." + nameof(IStyle.unitySliceTop); + internal static readonly DataBindingProperty unityTextAlignProperty = "style." + nameof(IStyle.unityTextAlign); + internal static readonly DataBindingProperty unityTextOutlineColorProperty = "style." + nameof(IStyle.unityTextOutlineColor); + internal static readonly DataBindingProperty unityTextOutlineWidthProperty = "style." + nameof(IStyle.unityTextOutlineWidth); + internal static readonly DataBindingProperty unityTextOverflowPositionProperty = "style." + nameof(IStyle.unityTextOverflowPosition); + internal static readonly DataBindingProperty visibilityProperty = "style." + nameof(IStyle.visibility); + internal static readonly DataBindingProperty whiteSpaceProperty = "style." + nameof(IStyle.whiteSpace); + internal static readonly DataBindingProperty widthProperty = "style." + nameof(IStyle.width); + internal static readonly DataBindingProperty wordSpacingProperty = "style." + nameof(IStyle.wordSpacing); + } + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/TemplateContainer.cs b/ModuleOverrides/com.unity.ui/Core/TemplateContainer.cs index 7afa1e384..65589533f 100644 --- a/ModuleOverrides/com.unity.ui/Core/TemplateContainer.cs +++ b/ModuleOverrides/com.unity.ui/Core/TemplateContainer.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System.Collections.Generic; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -17,6 +18,9 @@ namespace UnityEngine.UIElements /// public class TemplateContainer : BindableElement { + internal static readonly DataBindingProperty templateIdProperty = nameof(templateId); + internal static readonly DataBindingProperty templateSourceProperty = nameof(templateSource); + /// /// Instantiates and clones a using the data read from a UXML file. /// @@ -88,6 +92,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// The local ID of the template in the parent UXML file (RO). /// /// This value is null, unless the TemplateContainer represents a UXML template within another UXML file. + [CreateProperty(ReadOnly = true)] public string templateId { get; private set; } private VisualElement m_ContentContainer; @@ -97,6 +102,7 @@ public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext /// Stores the template asset reference, if the generated element is cloned from a VisualTreeAsset as a /// Template declaration inside another VisualTreeAsset. /// + [CreateProperty(ReadOnly = true)] public VisualTreeAsset templateSource { get => m_TemplateSource; diff --git a/ModuleOverrides/com.unity.ui/Core/Text/PanelTextSettings.cs b/ModuleOverrides/com.unity.ui/Core/Text/PanelTextSettings.cs index 12c1b7c29..293664d3a 100644 --- a/ModuleOverrides/com.unity.ui/Core/Text/PanelTextSettings.cs +++ b/ModuleOverrides/com.unity.ui/Core/Text/PanelTextSettings.cs @@ -25,46 +25,11 @@ internal static PanelTextSettings defaultPanelTextSettings { if (s_DefaultPanelTextSettings == null) { - s_DefaultPanelTextSettings = EditorGUIUtilityLoad(s_DefaultEditorPanelTextSettingPath) as PanelTextSettings; - UpdateLocalizationFontAsset(); - - if (s_DefaultPanelTextSettings == null) - s_DefaultPanelTextSettings = ScriptableObject.CreateInstance(); + s_DefaultPanelTextSettings = ScriptableObject.CreateInstance(); } return s_DefaultPanelTextSettings; } } - - internal static void UpdateLocalizationFontAsset() - { - string platform = " - Linux"; - var localizationAssetPathPerSystemLanguage = new Dictionary() - { - { SystemLanguage.English, Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/English{platform}.asset") }, - { SystemLanguage.Japanese, Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/Japanese{platform}.asset") }, - { SystemLanguage.ChineseSimplified, Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/ChineseSimplified{platform}.asset") }, - { SystemLanguage.ChineseTraditional, Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/ChineseTraditional{platform}.asset") }, - { SystemLanguage.Korean, Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/Localization/Korean{platform}.asset") } - }; - - var globalFallbackAssetPath = Path.Combine(UIElementsPackageUtility.EditorResourcesBasePath, $"UIPackageResources/FontAssets/DynamicOSFontAssets/GlobalFallback/GlobalFallback{platform}.asset"); - - var localizationAsset = EditorGUIUtilityLoad(localizationAssetPathPerSystemLanguage[GetCurrentLanguage()]) as FontAsset; - var globalFallbackAsset = EditorGUIUtilityLoad(globalFallbackAssetPath) as FontAsset; - - defaultPanelTextSettings.fallbackFontAssets[0] = localizationAsset; - defaultPanelTextSettings.fallbackFontAssets[defaultPanelTextSettings.fallbackFontAssets.Count - 1] = globalFallbackAsset; - } - - internal FontAsset GetCachedFontAsset(Font font) - { - return GetCachedFontAssetInternal(font); - } - - internal static Func EditorGUIUtilityLoad; - internal static Func GetCurrentLanguage; - - internal static readonly string s_DefaultEditorPanelTextSettingPath = "UIPackageResources/Default Editor Text Settings.asset"; } } diff --git a/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs b/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs index 64d343106..8c1bcfc20 100644 --- a/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs +++ b/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs @@ -361,6 +361,9 @@ internal bool TextLibraryCanElide() internal static class TextUtilities { + public static Func getEditorTextSettings; + public static Func getEditorTextSharpness; + internal static Vector2 MeasureVisualElementTextSize(TextElement te, string textToMeasure, float width, VisualElement.MeasureMode widthMode, float height, VisualElement.MeasureMode heightMode) { float measuredWidth = float.NaN; @@ -420,9 +423,9 @@ internal static FontAsset GetFontAsset(VisualElement ve) var textSettings = GetTextSettingsFrom(ve); if (ve.computedStyle.unityFontDefinition.font != null) - return textSettings.GetCachedFontAsset(ve.computedStyle.unityFontDefinition.font); + return textSettings.GetCachedFontAsset(ve.computedStyle.unityFontDefinition.font, TextShaderUtilities.ShaderRef_MobileSDF); else if (ve.computedStyle.unityFont != null) - return textSettings.GetCachedFontAsset(ve.computedStyle.unityFont); + return textSettings.GetCachedFontAsset(ve.computedStyle.unityFont, TextShaderUtilities.ShaderRef_MobileSDF); return null; } @@ -442,11 +445,11 @@ internal static bool IsFontAssigned(VisualElement ve) return ve.computedStyle.unityFont != null || !ve.computedStyle.unityFontDefinition.IsEmpty(); } - internal static PanelTextSettings GetTextSettingsFrom(VisualElement ve) + internal static TextSettings GetTextSettingsFrom(VisualElement ve) { if (ve.panel is RuntimePanel runtimePanel) return runtimePanel.panelSettings.textSettings ?? PanelTextSettings.defaultPanelTextSettings; - return PanelTextSettings.defaultPanelTextSettings; + return getEditorTextSettings(); } internal static TextCoreSettings GetTextCoreSettingsForElement(VisualElement ve) diff --git a/ModuleOverrides/com.unity.ui/Core/TextElement.cs b/ModuleOverrides/com.unity.ui/Core/TextElement.cs index 4203ddfd7..f42eadbc0 100644 --- a/ModuleOverrides/com.unity.ui/Core/TextElement.cs +++ b/ModuleOverrides/com.unity.ui/Core/TextElement.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine.UIElements.UIR; namespace UnityEngine.UIElements @@ -21,6 +22,13 @@ internal interface ITextElement /// public partial class TextElement : BindableElement, ITextElement, INotifyValueChanged { + internal static readonly DataBindingProperty textProperty = nameof(text); + internal static readonly DataBindingProperty enableRichTextProperty = nameof(enableRichText); + internal static readonly DataBindingProperty parseEscapeSequencesProperty = nameof(parseEscapeSequences); + internal static readonly DataBindingProperty isElidedProperty = nameof(isElided); + internal static readonly DataBindingProperty displayTooltipWhenElidedProperty = nameof(displayTooltipWhenElided); + internal static readonly DataBindingProperty valueProperty = nameof(value); + /// /// Instantiates a using the data read from a UXML file. /// @@ -143,7 +151,7 @@ private void OnDetachFromPanel(DetachFromPanelEvent detachEvent) (detachEvent.originPanel as BaseVisualElementPanel)?.liveReloadSystem.UnregisterTextElement(this); } - [SerializeField] + [SerializeField, DontCreateProperty] private string m_Text = String.Empty; /// @@ -152,6 +160,7 @@ private void OnDetachFromPanel(DetachFromPanelEvent detachEvent) /// /// Changing this value will implicitly invoke the setter, which will raise a of type string. /// + [CreateProperty] public virtual string text { get => ((INotifyValueChanged) this).value; @@ -163,6 +172,7 @@ public virtual string text /// /// When false, rich text tags will not be parsed. /// + [CreateProperty] public bool enableRichText { get => m_EnableRichText; @@ -171,6 +181,7 @@ public bool enableRichText if (m_EnableRichText == value) return; m_EnableRichText = value; MarkDirtyRepaint(); + NotifyPropertyChanged(enableRichTextProperty); } } @@ -178,6 +189,7 @@ public bool enableRichText /// /// Specifies whether escape sequences are displayed as is or if they are replaced by the character they represent. /// + [CreateProperty] public bool parseEscapeSequences { get => m_ParseEscapeSequences; @@ -187,6 +199,7 @@ public bool parseEscapeSequences m_ParseEscapeSequences = value; MarkDirtyRepaint(); + NotifyPropertyChanged(parseEscapeSequencesProperty); } } @@ -196,6 +209,7 @@ public bool parseEscapeSequences /// When true, a tooltip displays the full version of elided text, and also if a tooltip had been previously /// provided, it will be overwritten. /// + [CreateProperty] public bool displayTooltipWhenElided { get => m_DisplayTooltipWhenElided; @@ -206,6 +220,7 @@ public bool displayTooltipWhenElided m_DisplayTooltipWhenElided = value; UpdateVisibleText(); MarkDirtyRepaint(); + NotifyPropertyChanged(displayTooltipWhenElidedProperty); } } } @@ -222,6 +237,7 @@ public bool displayTooltipWhenElided /// /// The text Element hides elided text, and displays an ellipsis ('...') to indicate that there is hidden overflow content. /// + [CreateProperty(ReadOnly = true)] public bool isElided { get; private set; } internal static readonly string k_EllipsisText = @"..."; // Some web standards seem to suggest "\u2026" (horizontal ellipsis Unicode character) @@ -421,6 +437,9 @@ string INotifyValueChanged.value evt.elementTarget = this; ((INotifyValueChanged) this).SetValueWithoutNotify(value); SendEvent(evt); + NotifyPropertyChanged(valueProperty); + // We fire the property changed for text here because text simply assigns the value. + NotifyPropertyChanged(textProperty); } } else @@ -431,6 +450,13 @@ string INotifyValueChanged.value } } + [CreateProperty] + private string value + { + get => ((INotifyValueChanged) this).value; + set => ((INotifyValueChanged) this).value = value; + } + void INotifyValueChanged.SetValueWithoutNotify(string newValue) { newValue = ((ITextEdition)this).CullString(newValue); diff --git a/ModuleOverrides/com.unity.ui/Core/TextElementEdition.cs b/ModuleOverrides/com.unity.ui/Core/TextElementEdition.cs index f99c176a5..e2d8e76be 100644 --- a/ModuleOverrides/com.unity.ui/Core/TextElementEdition.cs +++ b/ModuleOverrides/com.unity.ui/Core/TextElementEdition.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Unity.Properties; using UnityEngine.UIElements.UIR; namespace UnityEngine.UIElements @@ -128,6 +129,14 @@ public TouchScreenKeyboardType keyboardType // Text editing and selection management implementation public partial class TextElement : ITextEdition { + internal static readonly DataBindingProperty autoCorrectionProperty = nameof(autoCorrection); + internal static readonly DataBindingProperty hideMobileInputProperty = nameof(hideMobileInput); + internal static readonly DataBindingProperty keyboardTypeProperty = nameof(keyboardType); + internal static readonly DataBindingProperty isReadOnlyProperty = nameof(isReadOnly); + internal static readonly DataBindingProperty isPasswordProperty = nameof(isPassword); + internal static readonly DataBindingProperty maxLengthProperty = nameof(maxLength); + internal static readonly DataBindingProperty maskCharProperty = nameof(maskChar); + /// /// Retrieves this TextElement's ITextEdition /// @@ -163,7 +172,20 @@ TouchScreenKeyboard ITextEdition.touchScreenKeyboard TouchScreenKeyboardType ITextEdition.keyboardType { get => m_KeyboardType; - set => m_KeyboardType = value; + set + { + if (m_KeyboardType == value) + return; + m_KeyboardType = value; + NotifyPropertyChanged(keyboardTypeProperty); + } + } + + [CreateProperty] + private TouchScreenKeyboardType keyboardType + { + get => edition.keyboardType; + set => edition.keyboardType = value; } bool m_HideMobileInput; @@ -184,6 +206,7 @@ bool ITextEdition.hideMobileInput } set { + var current = m_HideMobileInput; switch(Application.platform) { case RuntimePlatform.Android: @@ -196,9 +219,18 @@ bool ITextEdition.hideMobileInput m_HideMobileInput = value; break; } + if (current != m_HideMobileInput) + NotifyPropertyChanged(hideMobileInputProperty); } } + [CreateProperty] + private bool hideMobileInput + { + get => edition.hideMobileInput; + set => edition.hideMobileInput = value; + } + bool m_IsReadOnly = true; bool ITextEdition.isReadOnly @@ -212,9 +244,17 @@ bool ITextEdition.isReadOnly editingManipulator = value ? null : new TextEditingManipulator(this); m_IsReadOnly = value; onIsReadOnlyChanged?.Invoke(value); + NotifyPropertyChanged(isReadOnlyProperty); } } + [CreateProperty] + private bool isReadOnly + { + get => edition.isReadOnly; + set => edition.isReadOnly = value; + } + void ProcessMenuCommand(string command) { using (ExecuteCommandEvent evt = ExecuteCommandEvent.GetPooled(command)) @@ -318,11 +358,21 @@ int ITextEdition.maxLength get => m_MaxLength; set { + if (m_MaxLength == value) + return; m_MaxLength = value; text = edition.CullString(text); + NotifyPropertyChanged(maxLengthProperty); } } + [CreateProperty] + private int maxLength + { + get => edition.maxLength; + set => edition.maxLength = value; + } + string m_PlaceholderText = ""; string ITextEdition.placeholder { @@ -407,10 +457,18 @@ char ITextEdition.maskChar { IncrementVersion(VersionChangeType.Repaint); } + NotifyPropertyChanged(maskCharProperty); } } } + [CreateProperty] + private char maskChar + { + get => edition.maskChar; + set => edition.maskChar = value; + } + char effectiveMaskChar { get => edition.isPassword ? m_MaskChar : Char.MinValue; @@ -425,10 +483,18 @@ bool ITextEdition.isPassword { m_IsPassword = value; IncrementVersion(VersionChangeType.Repaint); + NotifyPropertyChanged(isPasswordProperty); } } } + [CreateProperty] + private bool isPassword + { + get => edition.isPassword; + set => edition.isPassword = value; + } + bool ITextEdition.hidePlaceholderOnFocus { get => m_HidePlaceholderTextOnFocus; @@ -449,7 +515,20 @@ internal bool showPlaceholderText bool ITextEdition.autoCorrection { get => m_AutoCorrection; - set => m_AutoCorrection = value; + set + { + if (m_AutoCorrection == value) + return; + m_AutoCorrection = value; + NotifyPropertyChanged(autoCorrectionProperty); + } + } + + [CreateProperty] + private bool autoCorrection + { + get => edition.autoCorrection; + set => edition.autoCorrection = value; } string m_RenderedText; diff --git a/ModuleOverrides/com.unity.ui/Core/TextElementSelection.cs b/ModuleOverrides/com.unity.ui/Core/TextElementSelection.cs index 6dddc6911..2187bcd50 100644 --- a/ModuleOverrides/com.unity.ui/Core/TextElementSelection.cs +++ b/ModuleOverrides/com.unity.ui/Core/TextElementSelection.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -93,9 +94,22 @@ public interface ITextSelection // Text editing and selection management implementation public partial class TextElement : ITextSelection { + internal static readonly DataBindingProperty isSelectableProperty = nameof(isSelectable); + internal static readonly DataBindingProperty cursorIndexProperty = nameof(cursorIndex); + internal static readonly DataBindingProperty selectIndexProperty = nameof(selectIndex); + internal static readonly DataBindingProperty doubleClickSelectsWordProperty = nameof(doubleClickSelectsWord); + internal static readonly DataBindingProperty tripleClickSelectsLineProperty = nameof(tripleClickSelectsLine); + internal static readonly DataBindingProperty cursorPositionProperty = nameof(cursorPosition); + internal static readonly DataBindingProperty selectionColorProperty = nameof(selectionColor); + internal static readonly DataBindingProperty cursorColorProperty = nameof(cursorColor); + internal static readonly DataBindingProperty selectAllOnFocusProperty = nameof(selectAllOnFocus); + internal static readonly DataBindingProperty selectAllOnMouseUpProperty = nameof(selectAllOnMouseUp); + internal static readonly DataBindingProperty selectionProperty = nameof(selection); + /// /// Retrieves this TextElement's ITextSelection /// + [CreateProperty(ReadOnly = true)] public ITextSelection selection => this; TextSelectingManipulator m_SelectingManipulator; @@ -109,32 +123,63 @@ bool ITextSelection.isSelectable get => m_IsSelectable && focusable; set { + if (value == m_IsSelectable) + return; + focusable = value; m_IsSelectable = value; + NotifyPropertyChanged(isSelectableProperty); } } + [CreateProperty] + private bool isSelectable + { + get => selection.isSelectable; + set => selection.isSelectable = value; + } + int ITextSelection.cursorIndex { get => selection.isSelectable ? selectingManipulator.cursorIndex : -1; set { + var current = selection.cursorIndex; if (selection.isSelectable) selectingManipulator.cursorIndex = value; + + if (current != selection.cursorIndex) + NotifyPropertyChanged(cursorIndexProperty); } } + [CreateProperty] + private int cursorIndex + { + get => selection.cursorIndex; + set => selection.cursorIndex = value; + } int ITextSelection.selectIndex { get => selection.isSelectable ? selectingManipulator.selectIndex : -1; set { + var current = selection.selectIndex; if (selection.isSelectable) selectingManipulator.selectIndex = value; + if (current != selection.selectIndex) + NotifyPropertyChanged(selectIndexProperty); } } + [CreateProperty] + private int selectIndex + { + get => selection.selectIndex; + set => selection.selectIndex = value; + } + void ITextSelection.SelectAll() { if (selection.isSelectable) @@ -161,22 +206,101 @@ bool ITextSelection.HasSelection() return selection.isSelectable && selectingManipulator.HasSelection(); } - bool ITextSelection.doubleClickSelectsWord { get; set; } = true; + private bool m_DoubleClickSelectsWord = true; - bool ITextSelection.tripleClickSelectsLine { get; set; } = true; + bool ITextSelection.doubleClickSelectsWord + { + get => m_DoubleClickSelectsWord; + set + { + if (m_DoubleClickSelectsWord == value) + return; + m_DoubleClickSelectsWord = value; + NotifyPropertyChanged(doubleClickSelectsWordProperty); + } + } + + [CreateProperty] + private bool doubleClickSelectsWord + { + get => selection.doubleClickSelectsWord; + set => selection.doubleClickSelectsWord = value; + } + + private bool m_TripleClickSelectsLine = true; + + bool ITextSelection.tripleClickSelectsLine + { + get => m_TripleClickSelectsLine; + set + { + if (m_TripleClickSelectsLine == value) + return; + m_TripleClickSelectsLine = value; + NotifyPropertyChanged(tripleClickSelectsLineProperty); + } + } + + [CreateProperty] + private bool tripleClickSelectsLine + { + get => selection.tripleClickSelectsLine; + set => selection.tripleClickSelectsLine = value; + } + + private bool m_SelectAllOnFocus = false; /// /// Controls whether the element's content is selected upon receiving focus. /// - bool ITextSelection.selectAllOnFocus { get; set; } = false; + bool ITextSelection.selectAllOnFocus + { + get => m_SelectAllOnFocus; + set + { + if (m_SelectAllOnFocus == value) + return; + m_SelectAllOnFocus = value; + NotifyPropertyChanged(selectAllOnFocusProperty); + } + } + + [CreateProperty] + private bool selectAllOnFocus + { + get => selection.selectAllOnFocus; + set => selection.selectAllOnFocus = value; + } + + private bool m_SelectAllOnMouseUp = false; /// /// Controls whether the element's content is selected when you mouse up for the first time. /// - bool ITextSelection.selectAllOnMouseUp { get; set; } = false; + bool ITextSelection.selectAllOnMouseUp + { + get => m_SelectAllOnMouseUp; + set + { + if (m_SelectAllOnMouseUp == value) + return; + m_SelectAllOnMouseUp = value; + NotifyPropertyChanged(selectAllOnMouseUpProperty); + } + } + + [CreateProperty] + private bool selectAllOnMouseUp + { + get => selection.selectAllOnMouseUp; + set => selection.selectAllOnMouseUp = value; + } Vector2 ITextSelection.cursorPosition => uitkTextHandle.GetCursorPositionFromStringIndexUsingLineHeight(selection.cursorIndex) + contentRect.min; + [CreateProperty(ReadOnly = true)] + private Vector2 cursorPosition => selection.cursorPosition; + float ITextSelection.lineHeightAtCursorPosition => uitkTextHandle.GetLineHeightFromCharacterIndex(selection.cursorIndex); void ITextSelection.MoveTextEnd() @@ -185,12 +309,58 @@ void ITextSelection.MoveTextEnd() selectingManipulator.m_SelectingUtilities.MoveTextEnd(); } - Color ITextSelection.selectionColor { get; set; } = new Color(0.239f, 0.502f, 0.875f, 0.65f); + private Color m_SelectionColor = new Color(0.239f, 0.502f, 0.875f, 0.65f); + + Color ITextSelection.selectionColor + { + get => m_SelectionColor; + set + { + if (m_SelectionColor == value) + return; + m_SelectionColor = value; + NotifyPropertyChanged(selectionColorProperty); + } + } + + [CreateProperty] + private Color selectionColor + { + get => selection.selectionColor; + set => selection.selectionColor = value; + } + private Color m_CursorColor = new Color(0.706f, 0.706f, 0.706f, 1.0f); + Color ITextSelection.cursorColor { + get => m_CursorColor; + set + { + if (m_CursorColor == value) + return; + m_CursorColor = value; + NotifyPropertyChanged(cursorColorProperty); + } + } - Color ITextSelection.cursorColor { get; set; } = new Color(0.706f, 0.706f, 0.706f, 1.0f); + [CreateProperty] + private Color cursorColor + { + get => selection.cursorColor; + set => selection.cursorColor = value; + } - float ITextSelection.cursorWidth { get; set; } = 1.0f; + private float m_CursorWidth = 1.0f; + + float ITextSelection.cursorWidth + { + get => m_CursorWidth; + set + { + if (Mathf.Approximately(m_CursorWidth, value)) + return; + m_CursorWidth = value; + } + } // Always return a valid selecting manipulator and rely on isSelectable to use it/not internal TextSelectingManipulator selectingManipulator => diff --git a/ModuleOverrides/com.unity.ui/Core/UXML/UxmlUtility.cs b/ModuleOverrides/com.unity.ui/Core/UXML/UxmlUtility.cs new file mode 100644 index 000000000..5446afd33 --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/UXML/UxmlUtility.cs @@ -0,0 +1,33 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace UnityEngine.UIElements +{ + internal static class UxmlUtility + { + internal static List ParseStringListAttribute(string itemList) + { + if (string.IsNullOrEmpty(itemList.Trim())) + return null; + + // Here the choices is comma separated in the string... + var items = itemList.Split(','); + + if (items.Length != 0) + { + var result = new List(); + foreach (var item in items) + { + result.Add(item.Trim()); + } + + return result; + } + + return null; + } + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElement.cs b/ModuleOverrides/com.unity.ui/Core/VisualElement.cs index 669ac5e50..b08dad7d3 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualElement.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualElement.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; +using Unity.Properties; using JetBrains.Annotations; using UnityEngine.Assertions; using UnityEngine.Yoga; @@ -63,6 +61,9 @@ internal enum VisualElementFlags HierarchyDisplayed = 1 << 12, // Element style are computed StyleInitialized = 1 << 13, + // Element is not rendered, but we keep the generated geometry in case it is shown later + DisableRendering = 1 << 14, + // Element initial flags Init = WorldTransformDirty | WorldTransformInverseDirty | WorldClipDirty | BoundingBoxDirty | WorldBoundingBoxDirty | EventCallbackParentCategoriesDirty | HierarchyDisplayed } @@ -240,10 +241,18 @@ internal bool isCompositeRoot } } - internal bool isHierarchyDisplayed + // areAncestorsAndSelfDisplayed is a combination of the inherited display state and our own display state. + // (See UIRLayoutUpdater::UpdateHierarchyDisplayed() to understand how it is set.) + internal bool areAncestorsAndSelfDisplayed { get => (m_Flags & VisualElementFlags.HierarchyDisplayed) == VisualElementFlags.HierarchyDisplayed; - set => m_Flags = value ? m_Flags | VisualElementFlags.HierarchyDisplayed : m_Flags & ~VisualElementFlags.HierarchyDisplayed; + set + { + m_Flags = value ? m_Flags | VisualElementFlags.HierarchyDisplayed : m_Flags & ~VisualElementFlags.HierarchyDisplayed; + + if(value && ( renderChainData.pendingRepaint|| renderChainData.pendingHierarchicalRepaint)) + IncrementVersion(VersionChangeType.Repaint); + } } private static uint s_NextId; @@ -269,6 +278,7 @@ internal bool isHierarchyDisplayed /// /// This is the key used to save/load the view data from the view data store. Not setting this key will disable persistence for this . /// + [CreateProperty] public string viewDataKey { get { return m_ViewDataKey; } @@ -280,6 +290,8 @@ public string viewDataKey if (!string.IsNullOrEmpty(value)) IncrementVersion(VersionChangeType.ViewData); + + NotifyPropertyChanged(viewDataKeyProperty); } } } @@ -300,6 +312,7 @@ internal bool enableViewDataPersistence /// /// This property can be used to associate application-specific user data with this VisualElement. /// + [CreateProperty] public object userData { get @@ -307,7 +320,14 @@ public object userData TryGetPropertyInternal(userDataPropertyKey, out object value); return value; } - set { SetPropertyInternal(userDataPropertyKey, value); } + set + { + var previous = userData; + SetPropertyInternal(userDataPropertyKey, value); + + if (previous != userData) + NotifyPropertyChanged(userDataProperty); + } } public override bool canGrabFocus @@ -342,6 +362,7 @@ public override FocusController focusController /// Note that those hints do not affect behavioral or visual results, but only affect the overall performance of the panel and the elements within. /// It's advised to always consider specifying the proper , but keep in mind that some might be internally ignored under certain conditions (e.g. due to hardware limitations on the target platform). /// + [CreateProperty] public UsageHints usageHints { get @@ -370,6 +391,8 @@ public UsageHints usageHints if ((value & UsageHints.DynamicColor) != 0) renderHints |= RenderHints.DynamicColor; else renderHints &= ~RenderHints.DynamicColor; + + NotifyPropertyChanged(usageHintsProperty); } } @@ -484,6 +507,7 @@ internal bool isLayoutManual /// Before reading from this property, add it to a panel and wait for one frame to ensure that the element layout is computed. /// After the layout is computed, a will be sent on this element. /// + [CreateProperty(ReadOnly = true)] public Rect layout { get @@ -546,6 +570,7 @@ internal set /// In the box model used by UI Toolkit, the content area refers to the inner rectangle for displaying text and images. /// It excludes the borders and the padding. /// + [CreateProperty(ReadOnly = true)] public Rect contentRect { get @@ -674,6 +699,7 @@ internal void UpdateWorldBoundingBox() /// /// AABB after applying the world transform to rect. /// + [CreateProperty(ReadOnly = true)] public Rect worldBound { get @@ -687,6 +713,7 @@ public Rect worldBound /// /// AABB after applying the transform to the rect, but before applying the layout translation. /// + [CreateProperty(ReadOnly = true)] public Rect localBound { get @@ -738,6 +765,7 @@ internal bool isWorldTransformInverseDirty /// /// Multiplying the layout rect by this matrix is incorrect because it already contains the translation. /// + [CreateProperty(ReadOnly = true)] public Matrix4x4 worldTransform { get @@ -1040,10 +1068,23 @@ static bool IsPartOfCapturedChain(VisualElement self, in IEventHandler capturing return self.Contains(capturingElement as VisualElement); } + private PickingMode m_PickingMode; + /// /// Determines if this element can be pick during mouseEvents or queries. /// - public PickingMode pickingMode { get; set; } + [CreateProperty] + public PickingMode pickingMode + { + get => m_PickingMode; + set + { + if (m_PickingMode == value) + return; + m_PickingMode = value; + NotifyPropertyChanged(pickingModeProperty); + } + } /// /// The name of this VisualElement. @@ -1052,6 +1093,7 @@ static bool IsPartOfCapturedChain(VisualElement self, in IEventHandler capturing /// Use this property to write USS selectors that target a specific element. /// The standard practice is to give an element a unique name. /// + [CreateProperty] public string name { get { return m_Name; } @@ -1061,6 +1103,7 @@ public string name return; m_Name = value; IncrementVersion(VersionChangeType.StyleSheet); + NotifyPropertyChanged(nameProperty); } } @@ -1471,19 +1514,33 @@ private bool isParentEnabledInHierarchy /// /// This flag verifies if the element is enabled globally. A parent disabling its child VisualElement affects this variable. /// + [CreateProperty(ReadOnly = true)] public bool enabledInHierarchy { get { return (pseudoStates & PseudoStates.Disabled) != PseudoStates.Disabled; } } + private bool m_EnabledSelf; //Returns the local enabled state /// /// Returns true if the is enabled locally. /// /// - /// This flag isn't changed if the VisualElement is disabled implicitely by one of its parents. To verify this, use enabledInHierarchy. + /// This flag isn't changed if the VisualElement is disabled implicitly by one of its parents. To verify this, use enabledInHierarchy. /// - public bool enabledSelf { get; private set;} + [CreateProperty(ReadOnly = true)] + public bool enabledSelf + { + get => m_EnabledSelf; + private set + { + if (m_EnabledSelf != value) + { + m_EnabledSelf = value; + NotifyPropertyChanged(enabledSelfProperty); + } + } + } /// /// Changes the enabled state. A disabled VisualElement does not receive most events. @@ -1523,6 +1580,7 @@ void PropagateEnabledToChildren(bool value) /// /// /// + [CreateProperty] public bool visible { get @@ -1531,10 +1589,13 @@ public bool visible } set { + var previous = visible; // Note: this could causes an allocation because styles are copy-on-write // we might want to remove this setter altogether // so everything goes through style.visibility (and then it's documented in a single place) style.visibility = value ? Visibility.Visible : Visibility.Hidden; + if (previous != visible) + NotifyPropertyChanged(visibleProperty); } } diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementBindableProperties.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementBindableProperties.cs new file mode 100644 index 000000000..0fb163011 --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementBindableProperties.cs @@ -0,0 +1,28 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace UnityEngine.UIElements +{ + public partial class VisualElement + { + internal static readonly DataBindingProperty viewDataKeyProperty = nameof(viewDataKey); + internal static readonly DataBindingProperty userDataProperty = nameof(userData); + internal static readonly DataBindingProperty usageHintsProperty = nameof(usageHints); + internal static readonly DataBindingProperty layoutProperty = nameof(layout); + internal static readonly DataBindingProperty contentRectProperty = nameof(contentRect); + internal static readonly DataBindingProperty worldBoundProperty = nameof(worldBound); + internal static readonly DataBindingProperty localBoundProperty = nameof(localBound); + internal static readonly DataBindingProperty worldTransformProperty = nameof(worldTransform); + internal static readonly DataBindingProperty pickingModeProperty = nameof(pickingMode); + internal static readonly DataBindingProperty nameProperty = nameof(name); + internal static readonly DataBindingProperty enabledInHierarchyProperty = nameof(enabledInHierarchy); + internal static readonly DataBindingProperty enabledSelfProperty = nameof(enabledSelf); + internal static readonly DataBindingProperty visibleProperty = nameof(visible); + internal static readonly DataBindingProperty panelProperty = nameof(panel); + internal static readonly DataBindingProperty visualTreeAssetSourceProperty = nameof(visualTreeAssetSource); + internal static readonly DataBindingProperty childCountProperty = nameof(childCount); + internal static readonly DataBindingProperty styleSheetsProperty = nameof(styleSheets); + internal static readonly DataBindingProperty tooltipProperty = nameof(tooltip); + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementDataBinding.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementDataBinding.cs new file mode 100644 index 000000000..39fb1b19c --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementDataBinding.cs @@ -0,0 +1,90 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using Unity.Properties; +using UnityEngine.Assertions; +using UnityEngine.Pool; + +namespace UnityEngine.UIElements +{ + /// + /// Defines a property accessible for data binding. + /// + internal readonly struct DataBindingProperty + { + private readonly PropertyPath m_PropertyPath; + private readonly string m_Path; + + /// + /// Instantiate a new data binding property. + /// + /// The path of the property. + public DataBindingProperty(string path) + { + m_PropertyPath = new PropertyPath(path); + m_Path = path; + } + + /// + /// Instantiate a new data binding property. + /// + /// The path of the property. + public DataBindingProperty(PropertyPath path) + { + m_PropertyPath = path; + m_Path = path.ToString(); + } + + /// + /// Converts a to a . + /// + /// The property. + /// A path for the property. + public static implicit operator PropertyPath(DataBindingProperty vep) + { + return vep.m_PropertyPath; + } + + /// + /// Converts a to a . + /// + /// The property. + /// A path for the property. + public static implicit operator string(DataBindingProperty vep) + { + return vep.m_Path; + } + + /// + /// Converts a to a . + /// + /// The name of the property. + /// The property. + public static implicit operator DataBindingProperty(string name) + { + return new DataBindingProperty(name); + } + + /// + /// Converts a to a . + /// + /// The path to the property. + /// The property. + public static implicit operator DataBindingProperty(PropertyPath path) + { + return new DataBindingProperty(path); + } + + /// + /// Returns the data binding path as a string. + /// + /// The property path. + public override string ToString() + { + return m_Path; + } + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementFocusRing.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementFocusRing.cs index 40cd5d927..b2e2c5f5b 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualElementFocusRing.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementFocusRing.cs @@ -271,8 +271,8 @@ void BuildRingForScopeRecursive(VisualElement ve, ref int scopeIndex, List + [CreateProperty(ReadOnly = true)] public int childCount { get diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs index 57c4793b1..8b53b0d31 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementStyleAccess.cs @@ -70,6 +70,7 @@ public ICustomStyle customStyle /// /// Returns a that manipulates style sheets attached to this element. /// + [CreateProperty(ReadOnly = true)] public VisualElementStyleSheetSet styleSheets => new VisualElementStyleSheetSet(this); internal List styleSheetList; diff --git a/ModuleOverrides/com.unity.ui/Core/VisualElementTooltip.cs b/ModuleOverrides/com.unity.ui/Core/VisualElementTooltip.cs index 2c52708c5..5670c1c6b 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualElementTooltip.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualElementTooltip.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using Unity.Properties; namespace UnityEngine.UIElements { @@ -21,6 +22,7 @@ public partial class VisualElement /// /// Text to display inside an information box after the user hovers the element for a small amount of time. /// + [CreateProperty] public string tooltip { get @@ -41,7 +43,11 @@ public string tooltip RegisterCallback(SetTooltip); } + var tooltipText = GetProperty(tooltipPropertyKey) as string; + if (string.CompareOrdinal(tooltipText, value) == 0) + return; SetProperty(tooltipPropertyKey, value); + NotifyPropertyChanged(tooltipProperty); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/VisualTreeViewDataUpdater.cs b/ModuleOverrides/com.unity.ui/Core/VisualTreeViewDataUpdater.cs index 3eaa22456..1b8aab85e 100644 --- a/ModuleOverrides/com.unity.ui/Core/VisualTreeViewDataUpdater.cs +++ b/ModuleOverrides/com.unity.ui/Core/VisualTreeViewDataUpdater.cs @@ -2,7 +2,6 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; using System.Collections.Generic; using Unity.Profiling; @@ -21,8 +20,15 @@ internal class VisualTreeViewDataUpdater : BaseVisualTreeUpdater private static readonly ProfilerMarker s_ProfilerMarker = new ProfilerMarker(s_Description); public override ProfilerMarker profilerMarker => s_ProfilerMarker; + // Only Editor panels actually do something with the ViewData of VisualElements + // Skip the execution of the updater completely for Runtime UI (including when developing in the Editor) + public bool enabled => panel.contextType == ContextType.Editor; + public override void OnVersionChanged(VisualElement ve, VersionChangeType versionChangeType) { + if (!enabled) + return; + if ((versionChangeType & VersionChangeType.ViewData) != VersionChangeType.ViewData) return; @@ -34,6 +40,9 @@ public override void OnVersionChanged(VisualElement ve, VersionChangeType versio public override void Update() { + if (!enabled) + return; + if (m_Version == m_LastVersion) return; diff --git a/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingExtensions.cs b/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingExtensions.cs index 0288b1796..30b7e618a 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingExtensions.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingExtensions.cs @@ -1829,7 +1829,7 @@ private void SetBinding(BaseField c, SerializedObjectBindingContext contex int enumValueAsInt = property.intValue; - Enum value = GetEnumFromSerializedFromInt(manageType, enumValueAsInt); + Enum value = GetEnumFromSerializedFromInt(manageType, ref enumValueAsInt); if (c is EnumField) (c as EnumField).Init(value); @@ -1865,24 +1865,41 @@ private void SetBinding(BaseField c, SerializedObjectBindingContext contex } } - static Enum GetEnumFromSerializedFromInt(Type managedType, int enumValueAsInt) + static Enum GetEnumFromSerializedFromInt(Type managedType, ref int enumValueAsInt) { var enumData = EnumDataUtility.GetCachedEnumData(managedType); if (enumData.flags) return EnumDataUtility.IntToEnumFlags(managedType, enumValueAsInt); - else + int valueIndex = Array.IndexOf(enumData.flagValues, enumValueAsInt); + + if (valueIndex != -1) + return enumData.values[valueIndex]; + + // For binding, return the minimal default value if enumValueAsInt is smaller than the smallest enum value, + // especially if no default enum is defined + if (enumData.flagValues.Length != 0) { - int valueIndex = Array.IndexOf(enumData.flagValues, enumValueAsInt); + var minIntValue = enumData.flagValues[0]; + var minIntValueIndex = 0; + for (int i = 1; i < enumData.flagValues.Length; i++) + { + if (enumData.flagValues[i] < minIntValue) + { + minIntValueIndex = i; + minIntValue = enumData.flagValues[i]; + } + } - if (valueIndex != -1) - return enumData.values[valueIndex]; - else + if (enumValueAsInt <= minIntValue || (enumValueAsInt == 0 && minIntValue < 0)) { - Debug.LogWarning("Error: invalid enum value " + enumValueAsInt + " for type " + managedType); - return null; + enumValueAsInt = minIntValue; + return enumData.values[minIntValueIndex]; } } + + Debug.LogWarning("Error: invalid enum value " + enumValueAsInt + " for type " + managedType); + return null; } protected override void SyncPropertyToField(BaseField c, SerializedProperty p) @@ -1897,7 +1914,7 @@ protected override void SyncPropertyToField(BaseField c, SerializedPropert } int enumValueAsInt = p.intValue; - field.value = GetEnumFromSerializedFromInt(managedType, enumValueAsInt); + field.value = GetEnumFromSerializedFromInt(managedType, ref enumValueAsInt); lastEnumValue = enumValueAsInt; } diff --git a/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs b/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs index b2ddd87e9..d125ed5b0 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs @@ -166,7 +166,7 @@ void BindListViewItem(VisualElement ve, int index) m_IsBinding = true; field.bindingPath = itemProp.propertyPath; - bindingContext.ContinueBinding(ve, itemProp); + bindingContext.ContinueBinding(ve, null); m_IsBinding = false; } @@ -420,7 +420,20 @@ public object this[int index] public bool IsFixedSize => true; - public int Count => properties.Count; + public int Count + { + get + { + if (ArrayProperty.serializedObject.isEditingMultipleObjects) + { + if (IsOverMaxMultiEditLimit) + return 0; + + return ArrayProperty.minArraySize; + } + return properties != null ? properties.Count : 0; + } + } bool ICollection.IsSynchronized { @@ -501,7 +514,7 @@ public void RemoveAt(int index) if (index >= 0 && index < Count) { var currentProperty = ArrayProperty.GetArrayElementAtIndex(index); - for (var i = index + 1; i < ArraySize.intValue; i++) + for (var i = index + 1; i < Count; i++) { var nextProperty = ArrayProperty.GetArrayElementAtIndex(i); if (nextProperty != null) diff --git a/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerSearchBar.cs b/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerSearchBar.cs index eda105f7f..303b6c7b2 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerSearchBar.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerSearchBar.cs @@ -181,7 +181,7 @@ private void PerformSearch(ChangeEvent evt) m_FoundItems.Add(new SearchResultItem() {item = treeItem, highlight = SearchHighlight.Type}); } - if ((m_CurrentFilter & DebuggerSearchBarFilter.Name) != 0 && + if ((m_CurrentFilter & DebuggerSearchBarFilter.Name) != 0 && !string.IsNullOrEmpty(element.name) && element.name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) { m_FoundItems.Add(new SearchResultItem() {item = treeItem, highlight = SearchHighlight.Name}); diff --git a/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerTreeView.cs b/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerTreeView.cs index 5dd855f02..e81ae6fe7 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerTreeView.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Debugger/DebuggerTreeView.cs @@ -30,6 +30,10 @@ public IEnumerable> treeItems { get { + if (m_TreeView?.viewController == null) + { + yield break; + } foreach (var itemId in m_TreeView.viewController.GetAllItemIds()) { yield return m_TreeViewController.GetTreeViewItemDataForId(itemId); diff --git a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs index cd346379c..42cfa11d4 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs @@ -194,8 +194,30 @@ private void DrawProperties() } m_SelectedElement.usageHints = (UsageHints)EditorGUILayout.EnumFlagsField("Usage Hints", m_SelectedElement.usageHints); + m_SelectedElement.tabIndex = EditorGUILayout.IntField("Tab Index", m_SelectedElement.tabIndex); EditorGUILayout.LabelField("Layout", m_SelectedElement.layout.ToString()); + EditorGUILayout.LabelField("LastLayout", m_SelectedElement.lastLayout.ToString()); + + if (GUILayout.Button("Increment Version Change - Repaint")) + { + m_SelectedElement.IncrementVersion(VersionChangeType.Repaint); + } + + if (Unsupported.IsDeveloperBuild()) + { + if (GUILayout.Button("Increment Version Change - Size")) + { + m_SelectedElement.IncrementVersion(VersionChangeType.Size); + } + if (GUILayout.Button("Increment Version Change - Transform")) + { + m_SelectedElement.IncrementVersion(VersionChangeType.Transform); + } + } + + EditorGUILayout.LabelField("Display", (m_SelectedElement.resolvedStyle.display == DisplayStyle.None) ? "None" : "Flex"); + EditorGUILayout.LabelField("World Bound", m_SelectedElement.worldBound.ToString()); EditorGUILayout.LabelField("World Clip", m_SelectedElement.worldClip.ToString()); EditorGUILayout.LabelField("Bounding Box", m_SelectedElement.boundingBox.ToString()); diff --git a/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs b/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs index 0bab0e6db..213e3dc73 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs @@ -13,8 +13,9 @@ internal static class EditorDelegateRegistration static EditorDelegateRegistration() { DefaultEventSystem.IsEditorRemoteConnected = () => EditorApplication.isRemoteConnected; - PanelTextSettings.EditorGUIUtilityLoad = path => EditorGUIUtility.Load(path); - PanelTextSettings.GetCurrentLanguage = () => LocalizationDatabase.currentEditorLanguage; + + TextUtilities.getEditorTextSettings = () => EditorTextSettings.defaultTextSettings; + TextUtilities.getEditorTextSharpness = (string fontAssetName) => EditorPrefs.GetFloat($"EditorTextSharpness_{fontAssetName}", 0.0f); UIDocument.IsEditorPlaying = () => EditorApplication.isPlaying; UIDocument.IsEditorPlayingOrWillChangePlaymode = () => EditorApplication.isPlayingOrWillChangePlaymode; diff --git a/ModuleOverrides/com.unity.ui/Editor/GameObjects/UIDocumentHierarchyWatcher.cs b/ModuleOverrides/com.unity.ui/Editor/GameObjects/UIDocumentHierarchyWatcher.cs index b862a31de..480a0f06f 100644 --- a/ModuleOverrides/com.unity.ui/Editor/GameObjects/UIDocumentHierarchyWatcher.cs +++ b/ModuleOverrides/com.unity.ui/Editor/GameObjects/UIDocumentHierarchyWatcher.cs @@ -27,7 +27,7 @@ static void OnHierarchyChanged() return; } - var uiDocuments = Object.FindObjectsOfType(); + var uiDocuments = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); // Early exit: no UIDocument to keep track of. if (uiDocuments == null || uiDocuments.Length == 0) diff --git a/Modules/AndroidJNI/AndroidAssetPacks.bindings.cs b/Modules/AndroidJNI/AndroidAssetPacks.bindings.cs index 67df6f4cb..d91bcaad7 100644 --- a/Modules/AndroidJNI/AndroidAssetPacks.bindings.cs +++ b/Modules/AndroidJNI/AndroidAssetPacks.bindings.cs @@ -324,6 +324,7 @@ public static class AndroidAssetPacks { public static bool coreUnityAssetPacksDownloaded { get { return CoreUnityAssetPacksDownloaded(); } } + internal static string textureCompressionsPackName { get { return GetTextureCompressionsPackName(); } } internal static string dataPackName { get { return GetDataPackName(); } } internal static string streamingAssetsPackName { get { return GetStreamingAssetsPackName(); } } @@ -343,6 +344,7 @@ public static void RemoveAssetPack(string assetPackName) {} // These values must match constants in AndroidAssetPacks.h // We can't directly access them since all code in PlatformDependent gets stripped when building source code delivery + private static string GetTextureCompressionsPackName() { return "UnityTextureCompressionsAssetPack"; } private static string GetDataPackName() { return "UnityDataAssetPack"; } private static string GetStreamingAssetsPackName() { return "UnityStreamingAssetsPack"; } } diff --git a/Modules/AssetDatabase/Editor/ScriptBindings/AssetOrigin.binding.cs b/Modules/AssetDatabase/Editor/ScriptBindings/AssetOrigin.binding.cs index bb2ed8646..ee6911292 100644 --- a/Modules/AssetDatabase/Editor/ScriptBindings/AssetOrigin.binding.cs +++ b/Modules/AssetDatabase/Editor/ScriptBindings/AssetOrigin.binding.cs @@ -8,6 +8,7 @@ namespace UnityEditor { + [Serializable] [StructLayout(LayoutKind.Sequential)] [NativeHeader("Modules/AssetDatabase/Editor/Public/AssetOrigin.h")] [NativeAsStruct] diff --git a/Modules/AssetPipelineEditor/Public/VideoImporter.bindings.cs b/Modules/AssetPipelineEditor/Public/VideoImporter.bindings.cs index d38b67664..b5f53e645 100644 --- a/Modules/AssetPipelineEditor/Public/VideoImporter.bindings.cs +++ b/Modules/AssetPipelineEditor/Public/VideoImporter.bindings.cs @@ -13,7 +13,7 @@ namespace UnityEditor { // AssetImporter for importing VideoClip - [NativeHeader("Editor/Src/Video/VideoClipTranscode.h")] + [NativeHeader("Modules/VideoEditor/VideoClipTranscode.h")] public enum VideoCodec { Auto = 0, @@ -38,7 +38,7 @@ public enum VideoEncodingProfile H264High = 2 } - [NativeHeader("Editor/Src/Video/VideoClipTranscode.h")] + [NativeHeader("Modules/VideoEditor/VideoClipTranscode.h")] public enum VideoDeinterlaceMode { Off = 0, @@ -46,7 +46,7 @@ public enum VideoDeinterlaceMode Odd = 2 } - [NativeHeader("Editor/Src/Video/VideoClipTranscode.h")] + [NativeHeader("Modules/VideoEditor/VideoClipTranscode.h")] internal enum VideoColorSpace { sRGB = 0, @@ -66,7 +66,7 @@ public enum VideoResizeMode CustomSize = 7 } - [NativeHeader("Editor/Src/Video/VideoClipTranscode.h")] + [NativeHeader("Modules/VideoEditor/VideoClipTranscode.h")] public enum VideoSpatialQuality { LowSpatialQuality = 0, diff --git a/Modules/BuildReportingEditor/Managed/BuildReport.bindings.cs b/Modules/BuildReportingEditor/Managed/BuildReport.bindings.cs index 10b4fe20c..132c5660c 100644 --- a/Modules/BuildReportingEditor/Managed/BuildReport.bindings.cs +++ b/Modules/BuildReportingEditor/Managed/BuildReport.bindings.cs @@ -93,5 +93,7 @@ internal TAppendix[] GetAppendicesByType() where TAppendix : Object [FreeFunction("BuildReporting::GetLatestReport")] internal static extern BuildReport GetLatestReport(); + + internal extern void SetBuildGUID(GUID guid); } } diff --git a/Modules/BuildReportingEditor/Managed/BuildSummary.cs b/Modules/BuildReportingEditor/Managed/BuildSummary.cs index 9af5fd34b..e45fee10b 100644 --- a/Modules/BuildReportingEditor/Managed/BuildSummary.cs +++ b/Modules/BuildReportingEditor/Managed/BuildSummary.cs @@ -36,6 +36,7 @@ public struct BuildSummary public BuildResult result { get; } internal BuildType buildType { get; } + public bool multiProcessEnabled { get; } private T ParseSubtarget() where T : Enum where S : Enum { diff --git a/Modules/EditorToolbar/Controls/EditorToolbarIcon.cs b/Modules/EditorToolbar/Controls/EditorToolbarIcon.cs new file mode 100644 index 000000000..d714fe6fc --- /dev/null +++ b/Modules/EditorToolbar/Controls/EditorToolbarIcon.cs @@ -0,0 +1,88 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEditor.Overlays; +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityEditor.Toolbars +{ + class EditorToolbarIcon : VisualElement + { + const string m_IconClassName = "unity-editor-toolbar-icon"; + const string m_TextIconClassName = EditorToolbar.elementTextIconClassName; + const string m_ImageIconClassName = EditorToolbar.elementIconClassName; + + TextElement m_TextIconElement; + Image m_IconElement; + + public string textIcon + { + get => m_TextIconElement?.text; + + set + { + if (string.IsNullOrEmpty(value)) + { + m_TextIconElement?.RemoveFromHierarchy(); + m_TextIconElement = null; + return; + } + + if (m_TextIconElement == null) + { + m_IconElement?.RemoveFromHierarchy(); + m_IconElement = null; + + m_TextIconElement = new TextElement(); + m_TextIconElement.AddToClassList(m_TextIconClassName); + Add(m_TextIconElement); + } + + m_TextIconElement.text = OverlayUtilities.GetSignificantLettersForIcon(value); + } + } + + public Texture2D icon + { + get => (Texture2D)m_IconElement?.image; + set + { + if (value == null) + { + m_IconElement?.RemoveFromHierarchy(); + m_IconElement = null; + return; + } + + if (m_IconElement == null) + { + m_TextIconElement?.RemoveFromHierarchy(); + m_TextIconElement = null; + + m_IconElement = new Image { scaleMode = ScaleMode.ScaleToFit }; + m_IconElement.AddToClassList(m_ImageIconClassName); + Add(m_IconElement); + } + + m_IconElement.image = value; + } + } + + public EditorToolbarIcon() : this(string.Empty, null) {} + public EditorToolbarIcon(string text) : this(text, null) {} + + public EditorToolbarIcon(Texture2D icon) : this(string.Empty, icon) {} + + private EditorToolbarIcon(string text, Texture2D icon) + { + AddToClassList(m_IconClassName); + + if (icon != null) + this.icon = icon; + else + this.textIcon = text; + } + } +} diff --git a/Modules/EditorToolbar/Controls/EditorToolbarToggle.cs b/Modules/EditorToolbar/Controls/EditorToolbarToggle.cs index f587734d1..7b9f7b3a6 100644 --- a/Modules/EditorToolbar/Controls/EditorToolbarToggle.cs +++ b/Modules/EditorToolbar/Controls/EditorToolbarToggle.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using UnityEditor.Overlays; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; @@ -10,8 +11,13 @@ namespace UnityEditor.Toolbars { public class EditorToolbarToggle : ToolbarToggle { + internal const string textIconClassName = EditorToolbar.elementTextIconClassName; internal const string textClassName = EditorToolbar.elementLabelClassName; internal const string iconClassName = EditorToolbar.elementIconClassName; + + internal const string k_TextIconElementName = "EditorToolbarToggleTextIcon"; + internal const string k_TextElementName = "EditorToolbarToggleText"; + public new const string ussClassName = "unity-editor-toolbar-toggle"; internal static readonly string toggleNoIconClassName = ussClassName + "-noicon"; @@ -19,12 +25,12 @@ public class EditorToolbarToggle : ToolbarToggle Texture2D m_OffIcon; TextElement m_TextElement; - readonly Image m_IconElement; + TextElement m_TextIconElement; + Image m_IconElement; public new string text { get => m_TextElement?.text; - set { if (string.IsNullOrEmpty(value)) @@ -36,12 +42,56 @@ public class EditorToolbarToggle : ToolbarToggle if (m_TextElement == null) { - Insert(IndexOf(m_IconElement)+1, m_TextElement = new TextElement()); + m_TextElement = new TextElement(); + m_TextElement.name = k_TextElementName; m_TextElement.AddToClassList(textClassName); + + var input = this.Q(className: Toggle.inputUssClassName); + if (m_IconElement != null) + input.Insert(input.IndexOf(m_IconElement) + 1, m_TextElement); + else if (m_TextIconElement != null) + input.Insert(input.IndexOf(m_TextIconElement) + 1, m_TextElement); + else + input.Add(m_TextElement); } m_TextElement.text = value; UpdateIconState(); + UpdateTextIconState(); + } + } + + public string textIcon + { + get => m_TextIconElement?.text; + set + { + if (m_TextIconElement == null) + { + m_IconElement?.RemoveFromHierarchy(); + m_IconElement = null; + m_OffIcon = null; + m_OnIcon = null; + + m_TextIconElement = new TextElement(); + m_TextIconElement.name = k_TextIconElementName; + m_TextIconElement.AddToClassList(textIconClassName); + + var input = this.Q(className: Toggle.inputUssClassName); + if (m_TextElement != null) + { + m_TextElement.RemoveFromHierarchy(); + input.Add(m_TextIconElement); + input.Add(m_TextElement); + } + else + { + input.Add(m_TextIconElement); + } + } + + m_TextIconElement.text = OverlayUtilities.GetSignificantLettersForIcon(value); + UpdateTextIconState(); } } @@ -50,6 +100,8 @@ public Texture2D icon get => offIcon; set { + CreateIconElement(); + offIcon = onIcon = value; UpdateIconState(); } @@ -60,6 +112,8 @@ public Texture2D onIcon get => m_OnIcon; set { + CreateIconElement(); + m_OnIcon = value; UpdateIconState(); } @@ -70,6 +124,8 @@ public Texture2D offIcon get => m_OffIcon; set { + CreateIconElement(); + m_OffIcon = value; UpdateIconState(); } @@ -84,19 +140,24 @@ public EditorToolbarToggle(Texture2D onIcon, Texture2D offIcon) : this(string.Em public EditorToolbarToggle(string text, Texture2D onIcon, Texture2D offIcon) { AddToClassList(ussClassName); - var input = this.Q(className: Toggle.inputUssClassName); - - m_IconElement = new Image { scaleMode = ScaleMode.ScaleToFit}; - m_IconElement.AddToClassList(iconClassName); - input.Add(m_IconElement); + this.onIcon = onIcon; + this.offIcon = offIcon; this.text = text; - m_OnIcon = onIcon; - m_OffIcon = offIcon; UpdateIconState(); } + public EditorToolbarToggle(string textIcon, string label) + { + AddToClassList(ussClassName); + + this.textIcon = textIcon; + this.text = label; + + UpdateTextIconState(); + } + public override void SetValueWithoutNotify(bool newValue) { base.SetValueWithoutNotify(newValue); @@ -105,20 +166,68 @@ public override void SetValueWithoutNotify(bool newValue) void UpdateIcon() { + if (m_IconElement == null) + return; + m_IconElement.image = value ? onIcon : offIcon; } void UpdateIconState() { + if (m_IconElement == null) + return; + if (icon == null && (text != null && text != string.Empty)) { if (!m_IconElement.ClassListContains(toggleNoIconClassName)) m_IconElement.AddToClassList(toggleNoIconClassName); } else if (icon && m_IconElement.ClassListContains(toggleNoIconClassName)) + { m_IconElement.RemoveFromClassList(toggleNoIconClassName); + } UpdateIcon(); } + + void UpdateTextIconState() + { + if (m_TextIconElement == null) + return; + + if ((m_TextIconElement == null || string.IsNullOrEmpty(textIcon)) && (text != null && text != string.Empty)) + { + if (!m_TextIconElement.ClassListContains(toggleNoIconClassName)) + m_TextIconElement.AddToClassList(toggleNoIconClassName); + } + else if (m_TextIconElement != null && m_TextIconElement.ClassListContains(toggleNoIconClassName)) + { + m_TextIconElement.RemoveFromClassList(toggleNoIconClassName); + } + } + + void CreateIconElement() + { + if (m_IconElement == null) + { + m_TextIconElement?.RemoveFromHierarchy(); + m_TextIconElement = null; + + var input = this.Q(className: Toggle.inputUssClassName); + m_IconElement = new Image { scaleMode = ScaleMode.ScaleToFit }; + m_IconElement.AddToClassList(iconClassName); + + if (m_TextElement != null) + { + m_TextElement.RemoveFromHierarchy(); + input.Add(m_IconElement); + input.Add(m_TextElement); + } + else + { + input.Add(m_IconElement); + } + } + } } } diff --git a/Modules/EditorToolbar/Controls/ToolButton.cs b/Modules/EditorToolbar/Controls/ToolButton.cs index 36f7d6670..19ed89f9d 100644 --- a/Modules/EditorToolbar/Controls/ToolButton.cs +++ b/Modules/EditorToolbar/Controls/ToolButton.cs @@ -334,7 +334,15 @@ void UpdateContent() case Tool.Custom: var content = EditorToolUtility.GetToolbarIcon(currentVariant); tooltip = L10n.Tr(content.tooltip); - onIcon = offIcon = content.image as Texture2D; + if (content.image == null && !string.IsNullOrEmpty(content.text)) + { + textIcon = content.text; + } + else + { + onIcon = offIcon = content.image as Texture2D; + text = content.text; + } break; } } diff --git a/Modules/EditorToolbar/Controls/ToolContextButton.cs b/Modules/EditorToolbar/Controls/ToolContextButton.cs index 653599b35..c0a4d9ca7 100644 --- a/Modules/EditorToolbar/Controls/ToolContextButton.cs +++ b/Modules/EditorToolbar/Controls/ToolContextButton.cs @@ -117,6 +117,8 @@ void RefreshActiveContext() } var content = EditorToolUtility.GetIcon(activeContextType, true); + if (content.image == null) + content.image = EditorGUIUtility.IconContent("CustomTool").image; icon = content.image as Texture2D; var activeContextName = EditorToolUtility.GetToolName(ToolManager.activeContextType) + (isGOToolContext ? " (Default)" : ""); diff --git a/Modules/EditorToolbar/ToolbarElements/EditorToolsToolbar.cs b/Modules/EditorToolbar/ToolbarElements/EditorToolsToolbar.cs index 1843dd349..21f7a4504 100644 --- a/Modules/EditorToolbar/ToolbarElements/EditorToolsToolbar.cs +++ b/Modules/EditorToolbar/ToolbarElements/EditorToolsToolbar.cs @@ -2,7 +2,9 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using System.Collections.Generic; +using System.Linq; using UnityEditor.EditorTools; using UnityEngine.UIElements; @@ -39,7 +41,7 @@ public BuiltinToolsStrip() { new VisualElement() { name = "Builtin View and Transform Tools" }, new VisualElement() { name = "Custom Global Tools" }, - new VisualElement() { name = "Component Tools" } + new VisualElement() }; for (int i = 0; i < k_ToolbarSections; ++i) @@ -71,25 +73,24 @@ void RebuildAvailableTools() foreach (var toolbar in m_Toolbars) toolbar.Clear(); - // Builtin tools are handled as a special case because order needs to be consistent - for (int i = (int)Tool.View; i < (int)Tool.Custom; ++i) - { - var tool = m_AvailableTools.FindIndex(x=> (Tool) x.scope == (Tool) i); - if(tool > -1) - defaultToolButtons.Add(new ToolButton((Tool) i, m_AvailableTools[tool].tools)); - } - - m_AvailableTools.Sort((a, b) => - { - if (a.priority == ToolAttribute.defaultPriority && b.priority == ToolAttribute.defaultPriority) - return a.GetHashCode().CompareTo(b.GetHashCode()); - return a.priority.CompareTo(b.priority); - }); + m_AvailableTools.OrderBy(x => x.scope) + .ThenBy(x => x.GetHashCode()) + .ThenBy(x => x.priority); + Type currentComponentHeaderType = null; + VisualElement componentTools = null; foreach (var entry in m_AvailableTools) { switch (entry.scope) { + case ToolEntry.Scope.BuiltinView: + case ToolEntry.Scope.BuiltinMove: + case ToolEntry.Scope.BuiltinRotate: + case ToolEntry.Scope.BuiltinScale: + case ToolEntry.Scope.BuiltinRect: + case ToolEntry.Scope.BuiltinTransform: + defaultToolButtons.Add(new ToolButton((Tool)entry.scope, entry.tools)); + break; case ToolEntry.Scope.BuiltinAdditional: defaultToolButtons.Add(new ToolButton(entry.tools)); break; @@ -97,13 +98,35 @@ void RebuildAvailableTools() customGlobalToolButtons.Add(new ToolButton(entry.tools)); break; case ToolEntry.Scope.Component: - componentToolButtons.Add(new ToolButton(entry.tools)); + if (currentComponentHeaderType == null || currentComponentHeaderType != entry.tools[0].target?.GetType()) + { + currentComponentHeaderType = entry.tools[0].target?.GetType(); + if (currentComponentHeaderType == null) + break; + + if (componentTools != null) + EditorToolbarUtility.SetupChildrenAsButtonStrip(componentTools); + + componentTools = new VisualElement() { name = "Component Tools" }; + componentTools.AddToClassList("toolbar-contents"); + + var header = new EditorToolbarIcon(); + if ((header.icon = EditorGUIUtility.FindTexture(currentComponentHeaderType)) == null) + header.textIcon = currentComponentHeaderType.Name; + + componentToolButtons.Add(header); + componentToolButtons.Add(componentTools); + } + componentTools.Add(new ToolButton(entry.tools)); break; } } - foreach(var toolbar in m_Toolbars) - EditorToolbarUtility.SetupChildrenAsButtonStrip(toolbar); + if (componentTools != null) + EditorToolbarUtility.SetupChildrenAsButtonStrip(componentTools); + + for (var i = 0; i < k_ToolbarSections - 1; i++) + EditorToolbarUtility.SetupChildrenAsButtonStrip(m_Toolbars[i]); } } } diff --git a/Modules/IMGUI/AssemblyInfo.cs b/Modules/IMGUI/AssemblyInfo.cs index ef119be4c..53c828ee8 100644 --- a/Modules/IMGUI/AssemblyInfo.cs +++ b/Modules/IMGUI/AssemblyInfo.cs @@ -10,3 +10,4 @@ [assembly: InternalsVisibleTo("Unity.UIElements.Editor")] [assembly: InternalsVisibleTo("Unity.UIElements.EditorTests")] [assembly: InternalsVisibleTo("Unity.UIElements.Tests")] +[assembly: InternalsVisibleTo("UnityEngine.UI.Tests")] diff --git a/Modules/IMGUI/DrawStates.cs b/Modules/IMGUI/DrawStates.cs index 4dc821949..a7f613b41 100644 --- a/Modules/IMGUI/DrawStates.cs +++ b/Modules/IMGUI/DrawStates.cs @@ -22,19 +22,19 @@ public DrawStates(int controlId, bool isHover, bool isActive, bool on, bool hasK hasTextInput = false; drawSelectionAsComposition = false; - cursorFirst = cursorLast = -1; + cursorFirstPosition = cursorLastPosition = Vector2.zero; selectionColor = cursorColor = Color.red; } public DrawStates(int controlId, bool isHover, bool isActive, bool on, bool hasKeyboardFocus, - bool drawSelectionAsComposition, int cursorFirst, int cursorLast, + bool drawSelectionAsComposition, Vector2 cursorFirstPosition, Vector2 cursorLastPosition, Color cursorColor, Color selectionColor) : this(controlId, isHover, isActive, on, hasKeyboardFocus) { hasTextInput = true; this.drawSelectionAsComposition = drawSelectionAsComposition; - this.cursorFirst = cursorFirst; - this.cursorLast = cursorLast; + this.cursorFirstPosition = cursorFirstPosition; + this.cursorLastPosition = cursorLastPosition; this.cursorColor = cursorColor; this.selectionColor = selectionColor; } @@ -47,8 +47,8 @@ public DrawStates(int controlId, bool isHover, bool isActive, bool on, bool hasK public readonly bool hasTextInput; public readonly bool drawSelectionAsComposition; - public readonly int cursorFirst; - public readonly int cursorLast; + public readonly Vector2 cursorFirstPosition; + public readonly Vector2 cursorLastPosition; public readonly Color cursorColor; public readonly Color selectionColor; } diff --git a/Modules/IMGUI/GUI.cs b/Modules/IMGUI/GUI.cs index 416cf7f22..61e99c6f2 100644 --- a/Modules/IMGUI/GUI.cs +++ b/Modules/IMGUI/GUI.cs @@ -603,7 +603,7 @@ internal static void DoTextField(Rect position, int id, GUIContent content, bool editor.SaveBackup(); editor.position = position; editor.style = style; - editor.multiline = multiline; + editor.isMultiline = multiline; editor.controlID = id; editor.DetectFocusChange(); @@ -789,6 +789,7 @@ private static void HandleTextFieldEventForDesktop(Rect position, int id, GUICon case EventType.Repaint: // If we have keyboard focus, draw the cursor // TODO: check if this OpenGL view has keyboard focus + editor.UpdateTextHandle(); if (GUIUtility.keyboardControl != id) { style.Draw(position, content, id, false); @@ -951,12 +952,17 @@ public static int Toolbar(Rect position, int selected, GUIContent[] contents, GU internal static int Toolbar(Rect position, int selected, GUIContent[] contents, string[] controlNames, GUIStyle style, ToolbarButtonSize buttonSize, bool[] contentsEnabled = null) { - GUIUtility.CheckOnGUI(); - // Get the styles here GUIStyle firstStyle, midStyle, lastStyle; FindStyles(ref style, out firstStyle, out midStyle, out lastStyle, "left", "mid", "right"); + return Toolbar(position, selected, contents, controlNames, style, firstStyle, midStyle, lastStyle, buttonSize, contentsEnabled); + } + + internal static int Toolbar(Rect position, int selected, GUIContent[] contents, string[] controlNames, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, ToolbarButtonSize buttonSize, bool[] contentsEnabled = null) + { + GUIUtility.CheckOnGUI(); + return DoButtonGrid(position, selected, contents, controlNames, contents.Length, style, firstStyle, midStyle, lastStyle, buttonSize, contentsEnabled); } diff --git a/Modules/IMGUI/GUIContent.cs b/Modules/IMGUI/GUIContent.cs index a2b8f2e5d..913f8875f 100644 --- a/Modules/IMGUI/GUIContent.cs +++ b/Modules/IMGUI/GUIContent.cs @@ -23,6 +23,8 @@ public class GUIContent Texture m_Image; [SerializeField] string m_Tooltip = string.Empty; + [SerializeField] + string m_TextWithWhitespace = string.Empty; internal event Action OnTextChanged; @@ -30,18 +32,41 @@ public class GUIContent private static readonly GUIContent s_Image = new GUIContent(); private static readonly GUIContent s_TextImage = new GUIContent(); + internal static string k_ZeroWidthSpace = "\u200B"; + // The text contained. public string text { get { return m_Text; } - set { - if (value == m_Text) + set + { + if (m_Text == value) return; + m_Text = value; + textWithWhitespace = value; OnTextChanged?.Invoke(); } } + internal string textWithWhitespace + { + get + { + return string.IsNullOrEmpty(m_TextWithWhitespace) ? k_ZeroWidthSpace : m_TextWithWhitespace; + } + set => + + //The NoWidthSpace unicode is added at the end of the string to make sure LineFeeds update the layout of the text. + m_TextWithWhitespace = value + k_ZeroWidthSpace; + } + + internal void SetTextWithoutNotify(string value) + { + m_Text = value; + textWithWhitespace = value; + } + // The icon image contained. public Texture image { @@ -118,6 +143,7 @@ internal int hash internal static GUIContent Temp(string t) { s_Text.m_Text = t; + s_Text.textWithWhitespace = t; s_Text.m_Tooltip = string.Empty; return s_Text; } @@ -125,6 +151,7 @@ internal static GUIContent Temp(string t) internal static GUIContent Temp(string t, string tooltip) { s_Text.m_Text = t; + s_Text.textWithWhitespace = t; s_Text.m_Tooltip = tooltip; return s_Text; } @@ -146,6 +173,7 @@ internal static GUIContent Temp(Texture i, string tooltip) internal static GUIContent Temp(string t, Texture i) { s_TextImage.m_Text = t; + s_Text.textWithWhitespace = t; s_TextImage.m_Image = i; return s_TextImage; } @@ -153,11 +181,14 @@ internal static GUIContent Temp(string t, Texture i) internal static void ClearStaticCache() { s_Text.m_Text = null; + s_Text.m_TextWithWhitespace = null; s_Text.m_Tooltip = string.Empty; s_Image.m_Image = null; s_Image.m_Tooltip = string.Empty; + s_Image.m_TextWithWhitespace = null; s_TextImage.m_Text = null; s_TextImage.m_Image = null; + s_TextImage.m_TextWithWhitespace = null; } internal static GUIContent[] Temp(string[] texts) diff --git a/Modules/IMGUI/GUILayout.cs b/Modules/IMGUI/GUILayout.cs index ddc009c61..51305bca0 100644 --- a/Modules/IMGUI/GUILayout.cs +++ b/Modules/IMGUI/GUILayout.cs @@ -121,12 +121,18 @@ static bool DoToggle(bool value, GUIContent content, GUIStyle style, GUILayoutOp public static int Toolbar(int selected, GUIContent[] contents, GUIStyle style, params GUILayoutOption[] options) { return Toolbar(selected, contents, style, GUI.ToolbarButtonSize.Fixed, options); } public static int Toolbar(int selected, GUIContent[] contents, GUIStyle style, GUI.ToolbarButtonSize buttonSize, params GUILayoutOption[] options) { return Toolbar(selected, contents, null, style, buttonSize, options); } public static int Toolbar(int selected, GUIContent[] contents, bool[] enabled, GUIStyle style, params GUILayoutOption[] options) { return Toolbar(selected, contents, enabled, style, GUI.ToolbarButtonSize.Fixed, options); } + internal static int Toolbar(int selected, GUIContent[] contents, bool[] enabled, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, params GUILayoutOption[] options) { return Toolbar(selected, contents, enabled, style, firstStyle, midStyle, lastStyle, GUI.ToolbarButtonSize.Fixed, options); } public static int Toolbar(int selected, GUIContent[] contents, bool[] enabled, GUIStyle style, GUI.ToolbarButtonSize buttonSize, params GUILayoutOption[] options) { GUIStyle firstStyle, midStyle, lastStyle; GUI.FindStyles(ref style, out firstStyle, out midStyle, out lastStyle, "left", "mid", "right"); + return Toolbar(selected, contents, enabled, style, firstStyle, midStyle, lastStyle, buttonSize, options); + } + + internal static int Toolbar(int selected, GUIContent[] contents, bool[] enabled, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, GUI.ToolbarButtonSize buttonSize, params GUILayoutOption[] options) + { Vector2 size = new Vector2(); int count = contents.Length; GUIStyle currentStyle = count > 1 ? firstStyle : style; @@ -173,7 +179,7 @@ public static int Toolbar(int selected, GUIContent[] contents, bool[] enabled, G break; } - return GUI.Toolbar(GUILayoutUtility.GetRect(size.x, size.y, style, options), selected, contents, null, style, buttonSize, enabled); + return GUI.Toolbar(GUILayoutUtility.GetRect(size.x, size.y, style, options), selected, contents, null, style, firstStyle, midStyle, lastStyle, buttonSize, enabled); } public static int SelectionGrid(int selected, string[] texts, int xCount, params GUILayoutOption[] options) { return SelectionGrid(selected, GUIContent.Temp(texts), xCount, GUI.skin.button, options); } diff --git a/Modules/IMGUI/GUIStyle.bindings.cs b/Modules/IMGUI/GUIStyle.bindings.cs index 71d57a81a..df326cfdd 100644 --- a/Modules/IMGUI/GUIStyle.bindings.cs +++ b/Modules/IMGUI/GUIStyle.bindings.cs @@ -3,8 +3,10 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Runtime.InteropServices; using UnityEngine.Bindings; using UnityEngine.Scripting; +using UnityEngine.TextCore.Text; namespace UnityEngine { @@ -60,9 +62,6 @@ partial class GUIStyle [FreeFunction(Name = "GUIStyle_Bindings::AssignRectOffset", HasExplicitThis = true)] private extern void AssignRectOffset(int idx, IntPtr srcRectOffset); - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetLineHeight")] - private static extern float Internal_GetLineHeight(IntPtr target); - [FreeFunction(Name = "GUIStyle_Bindings::Internal_Draw", HasExplicitThis = true)] private extern void Internal_Draw(Rect screenRect, GUIContent content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus); @@ -70,29 +69,13 @@ partial class GUIStyle private extern void Internal_Draw2(Rect position, GUIContent content, int controlID, bool on); [FreeFunction(Name = "GUIStyle_Bindings::Internal_DrawCursor", HasExplicitThis = true)] - private extern void Internal_DrawCursor(Rect position, GUIContent content, int pos, Color cursorColor); + private extern void Internal_DrawCursor(Rect position, GUIContent content, Vector2 pos, Color cursorColor); [FreeFunction(Name = "GUIStyle_Bindings::Internal_DrawWithTextSelection", HasExplicitThis = true)] private extern void Internal_DrawWithTextSelection(Rect screenRect, GUIContent content, bool isHover, bool isActive, - bool on, bool hasKeyboardFocus, bool drawSelectionAsComposition, int cursorFirst, int cursorLast, Color cursorColor, + bool on, bool hasKeyboardFocus, bool drawSelectionAsComposition, Vector2 cursorFirstPosition, Vector2 cursorLastPosition, Color cursorColor, Color selectionColor); - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetCursorPixelPosition", HasExplicitThis = true)] - internal extern Vector2 Internal_GetCursorPixelPosition(Rect position, GUIContent content, int cursorStringIndex); - - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetCursorStringIndex", HasExplicitThis = true)] - internal extern int Internal_GetCursorStringIndex(Rect position, GUIContent content, Vector2 cursorPixelPosition); - - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetSelectedRenderedText", HasExplicitThis = true)] - internal extern string Internal_GetSelectedRenderedText(Rect localPosition, GUIContent mContent, - int selectIndex, int cursorIndex); - - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetHyperlinksRect", HasExplicitThis = true)] - internal extern Rect[] Internal_GetHyperlinksRect(Rect localPosition, GUIContent mContent); - - [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetNumCharactersThatFitWithinWidth", HasExplicitThis = true)] - internal extern int Internal_GetNumCharactersThatFitWithinWidth(string text, float width); - [FreeFunction(Name = "GUIStyle_Bindings::Internal_CalcSize", HasExplicitThis = true)] internal extern Vector2 Internal_CalcSize(GUIContent content); @@ -107,15 +90,21 @@ internal extern string Internal_GetSelectedRenderedText(Rect localPosition, GUIC [FreeFunction(Name = "GUIStyle_Bindings::Internal_DrawPrefixLabel", HasExplicitThis = true)] private extern void Internal_DrawPrefixLabel(Rect position, GUIContent content, int controlID, bool on); - [FreeFunction(Name = "GUIStyle_Bindings::Internal_DrawContent", HasExplicitThis = true)] internal extern void Internal_DrawContent(Rect screenRect, GUIContent content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus, - bool hasTextInput, bool drawSelectionAsComposition, int cursorFirst, int cursorLast, Color cursorColor, Color selectionColor, + bool hasTextInput, bool drawSelectionAsComposition, Vector2 cursorFirst, Vector2 cursorLast, Color cursorColor, Color selectionColor, Color imageColor, float textOffsetX, float textOffsetY, float imageTopOffset, float imageLeftOffset, bool overflowX, bool overflowY); + [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetTextRectOffset", HasExplicitThis = true)] + internal extern Vector2 Internal_GetTextRectOffset(Rect screenRect, GUIContent content, Vector2 textSize); [FreeFunction(Name = "GUIStyle_Bindings::SetMouseTooltip")] internal static extern void SetMouseTooltip(string tooltip, Rect screenRect); [FreeFunction(Name = "GUIStyle_Bindings::IsTooltipActive")] internal static extern bool IsTooltipActive(string tooltip); [FreeFunction(Name = "GUIStyle_Bindings::Internal_GetCursorFlashOffset")] private static extern float Internal_GetCursorFlashOffset(); [FreeFunction(Name = "GUIStyle::SetDefaultFont")] internal static extern void SetDefaultFont(Font font); + + [FreeFunction(Name = "GUIStyle_Bindings::Internal_DestroyTextGenerator")] + internal static extern void Internal_DestroyTextGenerator(int meshInfoId); + [FreeFunction(Name = "GUIStyle_Bindings::Internal_CleanupAllTextGenerator")] + internal static extern void Internal_CleanupAllTextGenerator(); } } diff --git a/Modules/IMGUI/GUIStyle.cs b/Modules/IMGUI/GUIStyle.cs index 97ac13b1b..86db082cf 100644 --- a/Modules/IMGUI/GUIStyle.cs +++ b/Modules/IMGUI/GUIStyle.cs @@ -3,7 +3,10 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; using System.Runtime.InteropServices; +using UnityEngine.Scripting; +using UnityEngine.TextCore.Text; namespace UnityEngine { @@ -235,7 +238,7 @@ public RectOffset overflow } // The height of one line of text with this style, measured in pixels. - public float lineHeight => Mathf.Round(Internal_GetLineHeight(m_Ptr)); + public float lineHeight => Mathf.Round(IMGUITextHandle.GetLineHeight(this)); // Draw plain GUIStyle without text nor image. public void Draw(Rect position, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) @@ -308,7 +311,7 @@ internal void DrawPrefixLabel(Rect position, GUIContent content, int controlID) var drawStates = new DrawStates(controlID, position.Contains(Event.current.mousePosition), false, false, GUIUtility.HasKeyFocus(controlID)); if (onDraw == null || !onDraw(this, position, content, drawStates)) - Internal_DrawPrefixLabel(position, content, controlID, false); + Internal_DrawPrefixLabel(position, content, controlID, false); } else Debug.LogError("Style.DrawPrefixLabel may not be called with GUIContent that is null."); @@ -331,7 +334,7 @@ public void DrawCursor(Rect position, GUIContent content, int controlID, int cha if (cursorFlashSpeed == 0 || cursorFlashRel < .5f) cursorColor = GUI.skin.settings.cursorColor; - Internal_DrawCursor(position, content, character, cursorColor); + Internal_DrawCursor(position, content, GetCursorPixelPosition(position, content, character), cursorColor); } } @@ -344,6 +347,21 @@ internal void DrawWithTextSelection(Rect position, GUIContent content, bool isAc return; } + if (firstSelectedCharacter > lastSelectedCharacter) + { + var temp = lastSelectedCharacter; + lastSelectedCharacter = firstSelectedCharacter; + firstSelectedCharacter = temp; + } + + Vector2 firstSelectedCharacterPosition = GetCursorPixelPosition(position, content, firstSelectedCharacter); + Vector2 lastSelectedCharacterPosition = GetCursorPixelPosition(position, content, lastSelectedCharacter); + + // This offset is only required for IMGUI, so the change from TextNative->TextCore doesn't change the rendering + var imguiOffset = new Vector2(string.IsNullOrEmpty(content.text) ? 0f : 1f, 0f); + firstSelectedCharacterPosition -= imguiOffset; + lastSelectedCharacterPosition -= imguiOffset; + // Figure out the cursor color... Color cursorColor = new Color(0, 0, 0, 0); float cursorFlashSpeed = GUI.skin.settings.cursorFlashSpeed; @@ -353,11 +371,11 @@ internal void DrawWithTextSelection(Rect position, GUIContent content, bool isAc bool hovered = position.Contains(Event.current.mousePosition); var drawStates = new DrawStates(-1, hovered, isActive, false, hasKeyboardFocus, - drawSelectionAsComposition, firstSelectedCharacter, lastSelectedCharacter, cursorColor, selectionColor); + drawSelectionAsComposition, firstSelectedCharacterPosition, lastSelectedCharacterPosition, cursorColor, selectionColor); if (onDraw == null || !onDraw(this, position, content, drawStates)) { Internal_DrawWithTextSelection(position, content, hovered, isActive, false, hasKeyboardFocus, - drawSelectionAsComposition, firstSelectedCharacter, lastSelectedCharacter, cursorColor, selectionColor); + drawSelectionAsComposition, firstSelectedCharacterPosition, lastSelectedCharacterPosition, cursorColor, selectionColor); } } @@ -393,19 +411,29 @@ public static implicit operator GUIStyle(string str) // Get the pixel position of a given string index. public Vector2 GetCursorPixelPosition(Rect position, GUIContent content, int cursorStringIndex) { - return Internal_GetCursorPixelPosition(position, content, cursorStringIndex); + var handle = IMGUITextHandle.GetTextHandle(this, padding.Remove(position), content.textWithWhitespace, Color.white, true); + var cursorPos = handle.GetCursorPositionFromStringIndexUsingLineHeight(cursorStringIndex); + cursorPos = new Vector2(Mathf.Max(0.0f, cursorPos.x), cursorPos.y); + var rectOffset = Internal_GetTextRectOffset(position, content, new Vector2(handle.preferredSize.x, handle.preferredSize.y > 0 ? handle.preferredSize.y : lineHeight)); + return cursorPos + rectOffset - contentOffset - new Vector2(0, lineHeight); + } + + internal Rect[] GetHyperlinkRects(IMGUITextHandle handle, Rect content) + { + content = padding.Remove(content); + return handle.GetHyperlinkRects(content); } // Get the cursor position (indexing into contents.text) when the user clicked at cursorPixelPosition public int GetCursorStringIndex(Rect position, GUIContent content, Vector2 cursorPixelPosition) { - return Internal_GetCursorStringIndex(position, content, cursorPixelPosition); + return IMGUITextHandle.GetTextHandle(this, position, content.textWithWhitespace, Color.white, true).GetCursorIndexFromPosition(cursorPixelPosition); } // Returns number of characters that can fit within width, returns -1 if fails due to missing font internal int GetNumCharactersThatFitWithinWidth(string text, float width) { - return Internal_GetNumCharactersThatFitWithinWidth(text, width); + return IMGUITextHandle.GetTextHandle(this, new Rect(0, 0, width, 1), text, Color.white, true).GetNumCharactersThatFitWithinWidth(width); } // Calculate the size of a some content if it is rendered with this style. @@ -432,7 +460,13 @@ public Vector2 CalcScreenSize(Vector2 contentSize) // How tall this element will be when rendered with /content/ and a specific /width/. public float CalcHeight(GUIContent content, float width) { - return Internal_CalcHeight(content, width); + var height = Internal_CalcHeight(content, width); + return height; + } + + internal Vector2 GetPreferredSize(string content, Rect rect) + { + return IMGUITextHandle.GetTextHandle(this, padding.Remove(rect), content, Color.white, true).GetPreferredSize(); } public bool isHeightDependantOnWidth => fixedHeight == 0 && (wordWrap && imagePosition != ImagePosition.ImageOnly); @@ -449,6 +483,36 @@ public override string ToString() { return UnityString.Format("GUIStyle '{0}'", name); } + + [RequiredByNativeCode] + internal static void GetMeshInfo(GUIStyle style, Color color, string content, Rect rect, ref MeshInfoBindings[] meshInfos, ref Vector2 dimensions, ref int generationId) + { + var textHandle = IMGUITextHandle.GetTextHandle(style, rect, content, color); + var meshInfosRaw = textHandle.GetTextInfo(ref generationId).meshInfo; + meshInfos = new MeshInfoBindings[meshInfosRaw.Length]; + for (int i = 0; i < meshInfosRaw.Length; i++) + { + meshInfos[i] = new MeshInfoBindings() + { + vertexCount = meshInfosRaw[i].vertexCount, + vertexData = meshInfosRaw[i].vertexData, + material = meshInfosRaw[i].material + }; + } + dimensions = textHandle.preferredSize; + } + + [RequiredByNativeCode] + internal static void GetDimensions(GUIStyle style, Color color, string content, Rect rect, ref Vector2 dimensions) + { + dimensions = style.GetPreferredSize(content, rect); + } + + [RequiredByNativeCode] + internal static void GetLineHeight(GUIStyle style, ref float lineHeight) + { + lineHeight = style.lineHeight; + } } diff --git a/Modules/IMGUI/GUIUtility.cs b/Modules/IMGUI/GUIUtility.cs index 32063badb..914562aee 100644 --- a/Modules/IMGUI/GUIUtility.cs +++ b/Modules/IMGUI/GUIUtility.cs @@ -159,12 +159,13 @@ internal static void ProcessEvent(int instanceID, IntPtr nativeEventPtr, out boo result = false; if (processEvent != null) { - object[] parameters = { instanceID, nativeEventPtr}; // call manually so that the result is true if any of the invocation return true. // Otherwise only the return from the last invocation is used. foreach( var invocation in processEvent.GetInvocationList()) { - result = invocation.DynamicInvoke(parameters) is bool b && b || result; + if (invocation is not Func typed) + continue; + result |= typed.Invoke(instanceID, nativeEventPtr); } } } diff --git a/Modules/IMGUI/IMGUITextHandle.cs b/Modules/IMGUI/IMGUITextHandle.cs new file mode 100644 index 000000000..4f2aced7a --- /dev/null +++ b/Modules/IMGUI/IMGUITextHandle.cs @@ -0,0 +1,166 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine.TextCore.Text; + +namespace UnityEngine +{ + internal class IMGUITextHandle : TextHandle + { + const float sFallbackFontSize = 13; + const float sTimeToFlush = 1.0f; + const string kDefaultFontName = "LegacyRuntime.ttf"; + + internal static Func GetEditorTextSettings; + internal static Func GetEditorTextSharpness; + internal static Func GetEditorFont; + + private static IMGUITextHandle s_TextHandle = new IMGUITextHandle(); + + private static List textHandles = new List(); + + internal static void EmptyCache() + { + GUIStyle.Internal_CleanupAllTextGenerator(); + textHandles.Clear(); + } + + internal static IMGUITextHandle GetTextHandle(GUIStyle style, Rect position, string content, Color32 textColor, bool isOnlyForGeometry = false) + { + var settings = new TextCore.Text.TextGenerationSettings(); + ConvertGUIStyleToGenerationSettings(settings, style, textColor, content, position); + return GetTextHandle(settings, isOnlyForGeometry); + } + + private static IMGUITextHandle GetTextHandle(TextCore.Text.TextGenerationSettings settings, bool isOnlyForGeometry) + { + var currentTime = Time.realtimeSinceStartup; + bool isCached = false; + IMGUITextHandle textHandleCached = null; + int hash = isOnlyForGeometry ? settings.cachedGeomertyHashCode : settings.cachedHashCode; + + for (int i = textHandles.Count - 1; i >= 0; i--) + { + var textHandle = textHandles[i]; + var hash2 = isOnlyForGeometry ? textHandle.textGenerationSettings.cachedGeomertyHashCode : textHandle.textGenerationSettings.cachedHashCode; + if (hash == hash2) + { + textHandleCached = textHandle; + textHandle.lastTimeUsed = currentTime; + isCached = true; + } + + if (currentTime - textHandle.lastTimeUsed > sTimeToFlush) + { + GUIStyle.Internal_DestroyTextGenerator(textHandle.textGenerationSettings.cachedHashCode); + textHandles.RemoveAt(i); + } + } + + if (isCached) + return textHandleCached; + + var handle = new IMGUITextHandle(); + handle.lastTimeUsed = currentTime; + textHandles.Add(handle); + handle.Update(settings); + handle.UpdatePreferredSize(settings); + return handle; + } + + internal static float GetLineHeight(GUIStyle style) + { + var settings = new TextCore.Text.TextGenerationSettings(); + ConvertGUIStyleToGenerationSettings(settings, style, Color.white, "", Rect.zero); + return GetLineHeightDefault(settings); + } + + + internal TextInfo GetTextInfo(ref int id) + { + id = textGenerationSettings.cachedHashCode; + return textInfo; + } + + internal Vector2 GetPreferredSize() + { + return preferredSize; + } + + internal int GetNumCharactersThatFitWithinWidth(float width) + { + int characterCount = textInfo.lineInfo[0].characterCount; + int charCount; + float currentSize = 0; + + for (charCount = 0; charCount < characterCount; charCount++) + { + currentSize += textInfo.textElementInfo[charCount].xAdvance - textInfo.textElementInfo[charCount].origin; + if (currentSize > width) + { + break; + } + } + + return charCount; + } + + private static void ConvertGUIStyleToGenerationSettings(UnityEngine.TextCore.Text.TextGenerationSettings settings, GUIStyle style, Color textColor, string text, Rect rect) + { + if (settings.textSettings == null) + { + settings.textSettings = (TextSettings)GetEditorTextSettings(); + + if (settings.textSettings == null) + return; + } + + Font font = style.font; + + if (!font) + { + font = GetEditorFont(); + } + + settings.fontAsset = settings.textSettings.GetCachedFontAsset(font, TextShaderUtilities.ShaderRef_MobileSDF_IMGUI); + + if (settings.fontAsset == null) + return; + + settings.material = settings.fontAsset.material; + + // We only want to update the sharpness of the text in the editor with those preferences + settings.fontAsset.material.SetFloat("_Sharpness", GetEditorTextSharpness(style.font ? style.font.name : GetEditorFont().name)); + + settings.screenRect = new Rect(0, 0, rect.width, rect.height); + settings.text = text; + + settings.fontStyle = TextGeneratorUtilities.LegacyStyleToNewStyle(style.fontStyle); + settings.textAlignment = TextGeneratorUtilities.LegacyAlignmentToNewAlignment(style.alignment); + settings.wordWrap = rect.width > 0 ? style.wordWrap : false; + settings.wordWrappingRatio = 0.4f; + settings.richText = style.richText; + settings.parseControlCharacters = false; + + if (style.fontSize > 0) + settings.fontSize = style.fontSize; + else if (style.font) + settings.fontSize = style.font.fontSize; + else + settings.fontSize = sFallbackFontSize; + + settings.overflowMode = TextOverflowMode.Overflow; + settings.characterSpacing = 0; + settings.wordSpacing = 0; + settings.paragraphSpacing = 0; + settings.color = textColor; + + settings.inverseYAxis = true; + settings.shouldConvertToLinearSpace = false; + } + } +} diff --git a/Modules/IMGUI/RuntimeTextSettings.cs b/Modules/IMGUI/RuntimeTextSettings.cs new file mode 100644 index 000000000..27d378e89 --- /dev/null +++ b/Modules/IMGUI/RuntimeTextSettings.cs @@ -0,0 +1,31 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEngine.TextCore.Text; + +namespace UnityEngine +{ + /// + /// Represents text rendering settings for IMGUI runtime + /// + internal class RuntimeTextSettings : TextSettings + { + private static RuntimeTextSettings s_DefaultTextSettings; + + internal static RuntimeTextSettings defaultTextSettings + { + get + { + if (s_DefaultTextSettings == null) + { + s_DefaultTextSettings = ScriptableObject.CreateInstance(); + } + + return s_DefaultTextSettings; + } + } + } +} diff --git a/Modules/IMGUI/TextEditingUtilities.cs b/Modules/IMGUI/TextEditingUtilities.cs index 1dfe9e3ac..e391179c0 100644 --- a/Modules/IMGUI/TextEditingUtilities.cs +++ b/Modules/IMGUI/TextEditingUtilities.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using UnityEngine.Bindings; using UnityEngine.TextCore.Text; @@ -29,48 +30,71 @@ enum TextSelectOp internal class TextEditingUtilities { private TextSelectingUtilities m_TextSelectingUtility; - TextHandle m_TextHandle; + internal TextHandle textHandle; private bool hasSelection => m_TextSelectingUtility.hasSelection; private string SelectedText => m_TextSelectingUtility.selectedText; private int m_iAltCursorPos => m_TextSelectingUtility.iAltCursorPos; int m_CursorIndexSavedState = -1; internal bool isCompositionActive; bool m_UpdateImeWindowPosition; + internal Action OnTextChanged; public bool multiline = false; internal bool revealCursor { - get { return m_TextSelectingUtility.revealCursor; } - set { m_TextSelectingUtility.revealCursor = value; } + get => m_TextSelectingUtility.revealCursor; + set => m_TextSelectingUtility.revealCursor = value; } + + //Used by automated tests + internal int stringCursorIndex + { + get => textHandle.GetCorrespondingStringIndex(cursorIndex); + set => cursorIndex = textHandle.GetCorrespondingCodePointIndex(value); + } + private int cursorIndex { - get { return m_TextSelectingUtility.cursorIndex; } - set { m_TextSelectingUtility.cursorIndex = value; } + get => m_TextSelectingUtility.cursorIndex; + set => m_TextSelectingUtility.cursorIndex = value; } + + //Used by automated tests + internal int stringSelectIndex + { + get => textHandle.GetCorrespondingStringIndex(selectIndex); + set => selectIndex = textHandle.GetCorrespondingCodePointIndex(value); + } + private int selectIndex { - get { return m_TextSelectingUtility.selectIndex; } - set { m_TextSelectingUtility.selectIndex = value; } + get => m_TextSelectingUtility.selectIndex; + set => m_TextSelectingUtility.selectIndex = value; } string m_Text; public string text { - get { return m_Text; } + get => m_Text; set { if (value == m_Text) return; m_Text = value ?? string.Empty; + OnTextChanged?.Invoke(); } } + internal void SetTextWithoutNotify(string value) + { + m_Text = value; + } + public TextEditingUtilities(TextSelectingUtilities selectingUtilities, TextHandle textHandle, string text) { m_TextSelectingUtility = selectingUtilities; - m_TextHandle = textHandle; + this.textHandle = textHandle; m_Text = text; } @@ -104,7 +128,7 @@ public bool ShouldUpdateImeWindowPosition() public void SetImeWindowPosition(Vector2 worldPosition) { - var cursorPos = m_TextHandle.GetCursorPositionFromStringIndexUsingCharacterHeight(cursorIndex, true); + var cursorPos = textHandle.GetCursorPositionFromStringIndexUsingCharacterHeight(cursorIndex, true); GUIUtility.compositionCursorPos = worldPosition + cursorPos; } @@ -114,7 +138,7 @@ public string GeneratePreviewString(bool richText) var compositionString = GUIUtility.compositionString; if (isCompositionActive) { - return richText ? text.Insert(cursorIndex, $"{compositionString}") : text.Insert(cursorIndex, compositionString); + return richText ? text.Insert(stringCursorIndex, $"{compositionString}") : text.Insert(stringCursorIndex, compositionString); } return text; } @@ -311,22 +335,18 @@ public bool DeleteLineBack() DeleteSelection(); return true; } - int p = cursorIndex; - int i = p; - while (i-- != 0) - if (text[i] == '\n') - { - p = i + 1; - break; - } - if (i == -1) - p = 0; - if (cursorIndex != p) + + var currentLineInfo = textHandle.GetLineInfoFromCharacterIndex(cursorIndex); + var startIndex = currentLineInfo.firstCharacterIndex; + var stringStartIndex = textHandle.GetCorrespondingStringIndex(startIndex); + + if (startIndex != cursorIndex) { - text = text.Remove(p, cursorIndex - p); - m_TextSelectingUtility.selectIndex = cursorIndex = p; + text = text.Remove(stringStartIndex, stringCursorIndex - stringStartIndex); + cursorIndex = selectIndex = startIndex; return true; } + return false; } @@ -344,7 +364,8 @@ public bool DeleteWordBack() int prevWordEnd = m_TextSelectingUtility.FindEndOfPreviousWord(cursorIndex); if (cursorIndex != prevWordEnd) { - text = text.Remove(prevWordEnd, cursorIndex - prevWordEnd); + int prevWordEndString = textHandle.GetCorrespondingStringIndex(prevWordEnd); + text = text.Remove(prevWordEndString, stringCursorIndex - prevWordEndString); selectIndex = cursorIndex = prevWordEnd; return true; } @@ -365,7 +386,8 @@ public bool DeleteWordForward() int nextWordStart = m_TextSelectingUtility.FindStartOfNextWord(cursorIndex); if (cursorIndex < text.Length) { - text = text.Remove(cursorIndex, nextWordStart - cursorIndex); + int nextWordStartString = textHandle.GetCorrespondingStringIndex(nextWordStart); + text = text.Remove(stringCursorIndex, nextWordStartString - stringCursorIndex); return true; } return false; @@ -381,9 +403,10 @@ public bool Delete() DeleteSelection(); return true; } - else if (cursorIndex < text.Length) + else if (stringCursorIndex < text.Length) { - text = text.Remove(cursorIndex, m_TextSelectingUtility.NextCodePointIndex(cursorIndex) - cursorIndex); + var count = textHandle.textInfo.textElementInfo[cursorIndex].stringLength; + text = text.Remove(stringCursorIndex, count); return true; } return false; @@ -402,9 +425,10 @@ public bool Backspace() else if (cursorIndex > 0) { var startIndex = m_TextSelectingUtility.PreviousCodePointIndex(cursorIndex); - text = text.Remove(startIndex, cursorIndex - startIndex); - m_TextSelectingUtility.SetCursorIndexWithoutNotify(startIndex); - m_TextSelectingUtility.SetSelectIndexWithoutNotify(startIndex); + var count = textHandle.textInfo.textElementInfo[cursorIndex - 1].stringLength; + text = text.Remove(stringCursorIndex - count, count); + cursorIndex = startIndex; + selectIndex = startIndex; m_TextSelectingUtility.ClearCursorPos(); return true; } @@ -418,13 +442,13 @@ public bool DeleteSelection() return false; if (cursorIndex < selectIndex) { - text = text.Substring(0, cursorIndex) + text.Substring(selectIndex, text.Length - selectIndex); - m_TextSelectingUtility.SetSelectIndexWithoutNotify(cursorIndex); + text = text.Substring(0, stringCursorIndex) + text.Substring(stringSelectIndex, text.Length - stringSelectIndex); + selectIndex = cursorIndex; } else { - text = text.Substring(0, selectIndex) + text.Substring(cursorIndex, text.Length - cursorIndex); - m_TextSelectingUtility.SetCursorIndexWithoutNotify(selectIndex); + text = text.Substring(0, stringSelectIndex) + text.Substring(stringCursorIndex, text.Length - stringCursorIndex); + cursorIndex = selectIndex; } m_TextSelectingUtility.ClearCursorPos(); @@ -438,9 +462,9 @@ public void ReplaceSelection(string replace) DeleteSelection(); text = text.Insert(cursorIndex, replace); - var newIndex = cursorIndex + replace.Length; - m_TextSelectingUtility.SetCursorIndexWithoutNotify(newIndex); - m_TextSelectingUtility.SetSelectIndexWithoutNotify(newIndex); + var newIndex = cursorIndex + new StringInfo(replace).LengthInTextElements; + cursorIndex = newIndex; + selectIndex = newIndex; m_TextSelectingUtility.ClearCursorPos(); } diff --git a/Modules/IMGUI/TextEditor.cs b/Modules/IMGUI/TextEditor.cs index cca86b79a..044c29d76 100644 --- a/Modules/IMGUI/TextEditor.cs +++ b/Modules/IMGUI/TextEditor.cs @@ -6,517 +6,302 @@ using UnityEngine.Bindings; using UnityEngine.Scripting; using System.Collections.Generic; +using UnityEngine.TextCore.Text; namespace UnityEngine { public class TextEditor { + private readonly GUIContent m_Content = new GUIContent(); + private TextSelectingUtilities m_TextSelecting; + + //Used in automated tests + internal TextEditingUtilities m_TextEditing; + + //Used in automated tests + internal IMGUITextHandle m_TextHandle; + public TouchScreenKeyboard keyboardOnScreen = null; public int controlID = 0; - public GUIStyle style = GUIStyle.none; - public bool multiline = false; + + public GUIStyle style; + + [Obsolete("'multiline' has been deprecated. Changes to this member will not be observed. Use 'isMultiline' instead.", true)] + public bool multiline; + public bool isMultiline + { get => m_TextEditing.multiline; set => m_TextEditing.multiline = value; } + + [Obsolete("'hasHorizontalCursorPos' has been deprecated. Changes to this member will not be observed. Use 'hasHorizontalCursor' instead.", true)] public bool hasHorizontalCursorPos = false; + public bool hasHorizontalCursor + { + get => m_TextSelecting.hasHorizontalCursorPos; + set => m_TextSelecting.hasHorizontalCursorPos = value; + } + public bool isPasswordField = false; - internal bool m_HasFocus; - public Vector2 scrollOffset = Vector2.zero; // The text field can have a scroll offset in order to display its contents + // The text field can have a scroll offset in order to display its contents + public Vector2 scrollOffset; - private GUIContent m_Content = new GUIContent(); - private Rect m_Position; - private int m_CursorIndex = 0; - private int m_SelectIndex = 0; - private bool m_RevealCursor = false; + [Obsolete("'revealCursor' has been deprecated. Changes to this member will not be observed. Use 'showCursor' instead.", true)] + public bool revealCursor; - [Obsolete("Please use 'text' instead of 'content'", false)] + public bool showCursor + { + get => m_TextSelecting.revealCursor; + set => m_TextSelecting.revealCursor = value; + } + + private bool focus; + internal bool m_HasFocus { get { return focus; } set { focus = value; } } + + [Obsolete("Please use 'text' instead of 'content'", true)] public GUIContent content { - get { return m_Content; } - set { m_Content = value; } + get => throw new NotImplementedException("Please use 'text' instead of 'content'"); + set => throw new NotImplementedException("Please use 'text' instead of 'content'"); } public string text { - get { return m_Content.text; } + get => m_TextEditing.text; set { - m_Content.text = value ?? string.Empty; - EnsureValidCodePointIndex(ref m_CursorIndex); - EnsureValidCodePointIndex(ref m_SelectIndex); + if (m_TextEditing.text == value) + return; + m_TextEditing.SetTextWithoutNotify(value); + m_Content.SetTextWithoutNotify(value); + textWithWhitespace = value; + // TODO: Change this call to only do the parsing of the text, to update the characterCount properly. + UpdateTextHandle(); } } - public Rect position + string m_TextWithWhitespace; + internal string textWithWhitespace { - get { return m_Position; } - set - { - if (m_Position == value) - return; - - // Reset the scrollOffset to force its recomputation. - scrollOffset = Vector2.zero; - - m_Position = value; - - UpdateScrollOffset(); - } + get => string.IsNullOrEmpty(m_TextWithWhitespace) ? GUIContent.k_ZeroWidthSpace : m_TextWithWhitespace; + set => + //The NoWidthSpace unicode is added at the end of the string to make sure LineFeeds update the layout of the text. + m_TextWithWhitespace = value + GUIContent.k_ZeroWidthSpace; } + public Rect position { get; set; } + internal virtual Rect localPosition { - get { return position; } + get => style.padding.Remove(position); } public int cursorIndex { - get { return m_CursorIndex; } - set - { - int oldCursorIndex = m_CursorIndex; - m_CursorIndex = value; - EnsureValidCodePointIndex(ref m_CursorIndex); + get => m_TextSelecting.cursorIndex; + set => m_TextSelecting.cursorIndex = value; + } - if (m_CursorIndex != oldCursorIndex) - { - m_RevealCursor = true; - OnCursorIndexChange(); - } - } + internal int stringCursorIndex + { + get => m_TextEditing.stringCursorIndex; + set => m_TextEditing.stringCursorIndex = value; } public int selectIndex { - get { return m_SelectIndex; } - set - { - int oldSelectIndex = m_SelectIndex; - m_SelectIndex = value; - EnsureValidCodePointIndex(ref m_SelectIndex); + get => m_TextSelecting.selectIndex; + set => m_TextSelecting.selectIndex = value; + } - if (m_SelectIndex != oldSelectIndex) - OnSelectIndexChange(); - } + internal int stringSelectIndex + { + get => m_TextEditing.stringSelectIndex; + set => m_TextEditing.stringSelectIndex = value; } // are we up/downing? public Vector2 graphicalCursorPos; - public Vector2 graphicalSelectCursorPos; - // Clear the cursor position for vertical movement... - void ClearCursorPos() {hasHorizontalCursorPos = false; m_iAltCursorPos = -1; } + public Vector2 graphicalSelectCursorPos; - // selection - bool m_MouseDragSelectsWholeWords = false; - int m_DblClickInitPos = 0; - DblClickSnapping m_DblClickSnap = DblClickSnapping.WORDS; - public DblClickSnapping doubleClickSnapping { get { return m_DblClickSnap; } set { m_DblClickSnap = value; } } - bool m_bJustSelected = false; + public DblClickSnapping doubleClickSnapping + { + get => m_TextSelecting.dblClickSnap; + set => m_TextSelecting.dblClickSnap = value; + } - int m_iAltCursorPos = -1; - public int altCursorPosition { get { return m_iAltCursorPos; } set { m_iAltCursorPos = value; } } + public int altCursorPosition + { + get => m_TextSelecting.iAltCursorPos; + set => m_TextSelecting.iAltCursorPos = value; + } public enum DblClickSnapping : byte { WORDS, PARAGRAPHS } [RequiredByNativeCode] public TextEditor() { + var style = GUIStyle.none; + m_TextHandle = IMGUITextHandle.GetTextHandle(style, position, textWithWhitespace, Color.white, true); + m_TextSelecting = new TextSelectingUtilities(m_TextHandle); + m_TextEditing = new TextEditingUtilities(m_TextSelecting, m_TextHandle, m_Content.text); + m_Content.OnTextChanged += OnContentTextChangedHandle; + m_TextEditing.OnTextChanged += OnTextChangedHandle; + this.style = style; + + m_TextSelecting.OnCursorIndexChange += OnCursorIndexChange; + m_TextSelecting.OnSelectIndexChange += OnSelectIndexChange; } - public void OnFocus() + private void OnTextChangedHandle() { - if (multiline) - cursorIndex = selectIndex = 0; - else - SelectAll(); - m_HasFocus = true; + m_Content.SetTextWithoutNotify(text); + textWithWhitespace = text; } - public void OnLostFocus() + private void OnContentTextChangedHandle() { - m_HasFocus = false; - scrollOffset = Vector2.zero; + text = m_Content.text; + textWithWhitespace = text; } - void GrabGraphicalCursorPos() + public void OnFocus() { - if (!hasHorizontalCursorPos) - { - graphicalCursorPos = style.GetCursorPixelPosition(localPosition, m_Content, cursorIndex); - graphicalSelectCursorPos = style.GetCursorPixelPosition(localPosition, m_Content, selectIndex); - hasHorizontalCursorPos = false; - } + m_HasFocus = true; + m_TextSelecting.OnFocus(); } - // Handle a key event. - // Looks up the platform-dependent key-action table & performs the event - // return true if the event was recognized. - public bool HandleKeyEvent(Event e) + public void OnLostFocus() { - return HandleKeyEvent(e, false); + m_HasFocus = false; } - [VisibleToOtherModules] - internal bool HandleKeyEvent(Event e, bool textIsReadOnly) + public bool HasClickedOnLink(Vector2 mousePosition, out string linkData) { - InitKeyActions(); - EventModifiers m = e.modifiers; - e.modifiers &= ~EventModifiers.CapsLock; - if (s_Keyactions.ContainsKey(e)) + linkData = ""; + var intersectingLink = m_TextHandle.FindIntersectingLink(mousePosition - new Vector2(position.x, position.y)); + if (intersectingLink < 0) + return false; + + var link = m_TextHandle.textInfo.linkInfo[intersectingLink]; + if (link.linkId != null && link.linkIdLength > 0) { - TextEditOp op = (TextEditOp)s_Keyactions[e]; - PerformOperation(op, textIsReadOnly); - e.modifiers = m; + linkData = new string(link.linkId); return true; } - e.modifiers = m; return false; } - // Deletes previous text on the line - public bool DeleteLineBack() + public bool HasClickedOnHREF(Vector2 mousePosition, out string href) { - if (hasSelection) + href = ""; + var intersectingLink = m_TextHandle.FindIntersectingLink(mousePosition - new Vector2(position.x, position.y)); + if (intersectingLink < 0) + return false; + + var link = m_TextHandle.textInfo.linkInfo[intersectingLink]; + if (link.hashCode == (int)MarkupTag.HREF) { - DeleteSelection(); - return true; - } - int p = cursorIndex; - int i = p; - while (i-- != 0) - if (text[i] == '\n') + if (link.linkId != null && link.linkIdLength > 0) { - p = i + 1; - break; + href = new string(link.linkId); + if (Uri.IsWellFormedUriString(href, UriKind.Absolute)) + { + return true; + } } - if (i == -1) - p = 0; - if (cursorIndex != p) - { - m_Content.text = text.Remove(p, cursorIndex - p); - selectIndex = cursorIndex = p; - return true; } return false; } - // Deletes the previous word - public bool DeleteWordBack() + // Handle a key event. + // Looks up the platform-dependent key-action table & performs the event + // return true if the event was recognized. + public bool HandleKeyEvent(Event e) { - if (hasSelection) - { - DeleteSelection(); - return true; - } - - int prevWordEnd = FindEndOfPreviousWord(cursorIndex); - if (cursorIndex != prevWordEnd) - { - m_Content.text = text.Remove(prevWordEnd, cursorIndex - prevWordEnd); - selectIndex = cursorIndex = prevWordEnd; - return true; - } - return false; + return m_TextEditing.HandleKeyEvent(e) || m_TextSelecting.HandleKeyEvent(e); } - // Deletes the following word - public bool DeleteWordForward() - { - if (hasSelection) - { - DeleteSelection(); - return true; - } + // Deletes previous text on the line + public bool DeleteLineBack() => m_TextEditing.DeleteLineBack(); - int nextWordStart = FindStartOfNextWord(cursorIndex); - if (cursorIndex < text.Length) - { - m_Content.text = text.Remove(cursorIndex, nextWordStart - cursorIndex); - return true; - } - return false; - } + // Deletes the previous word + public bool DeleteWordBack() => m_TextEditing.DeleteWordBack(); + + // Deletes the following word + public bool DeleteWordForward() => m_TextEditing.DeleteWordForward(); // perform a right-delete - public bool Delete() - { - if (hasSelection) - { - DeleteSelection(); - return true; - } - else if (cursorIndex < text.Length) - { - m_Content.text = text.Remove(cursorIndex, NextCodePointIndex(cursorIndex) - cursorIndex); - return true; - } - return false; - } + public bool Delete() => m_TextEditing.Delete(); - public bool CanPaste() - { - return GUIUtility.systemCopyBuffer.Length != 0; - } + public bool CanPaste() => m_TextEditing.CanPaste(); // Perform a left-delete - public bool Backspace() - { - if (hasSelection) - { - DeleteSelection(); - return true; - } - else if (cursorIndex > 0) - { - var startIndex = PreviousCodePointIndex(cursorIndex); - m_Content.text = text.Remove(startIndex, cursorIndex - startIndex); - selectIndex = cursorIndex = startIndex; - ClearCursorPos(); - return true; - } - return false; - } + public bool Backspace() => m_TextEditing.Backspace(); /// Select all the text - public void SelectAll() - { - cursorIndex = 0; selectIndex = text.Length; - ClearCursorPos(); - } + public void SelectAll() => m_TextSelecting.SelectAll(); /// Select none of the text - public void SelectNone() - { - selectIndex = cursorIndex; - ClearCursorPos(); - } + public void SelectNone() => m_TextSelecting.SelectNone(); + /// Does this text field has a selection - public bool hasSelection { get { return cursorIndex != selectIndex; } } + public bool hasSelection => m_TextSelecting.hasSelection; /// Returns the selected text - public string SelectedText - { - get - { - if (cursorIndex == selectIndex) - return ""; - if (cursorIndex < selectIndex) - return text.Substring(cursorIndex, selectIndex - cursorIndex); - else - return text.Substring(selectIndex, cursorIndex - selectIndex); - } - } + public string SelectedText => m_TextSelecting.selectedText; /// Delete the current selection. If there is no selection, this function does not do anything... - public bool DeleteSelection() - { - if (cursorIndex == selectIndex) - return false; - if (cursorIndex < selectIndex) - { - m_Content.text = text.Substring(0, cursorIndex) + text.Substring(selectIndex, text.Length - selectIndex); - selectIndex = cursorIndex; - } - else - { - m_Content.text = text.Substring(0, selectIndex) + text.Substring(cursorIndex, text.Length - cursorIndex); - cursorIndex = selectIndex; - } - ClearCursorPos(); - - return true; - } + public bool DeleteSelection() => m_TextEditing.DeleteSelection(); /// Replace the selection with /replace/. If there is no selection, /replace/ is inserted at the current cursor point. - public void ReplaceSelection(string replace) - { - DeleteSelection(); - m_Content.text = text.Insert(cursorIndex, replace); - selectIndex = cursorIndex += replace.Length; - ClearCursorPos(); - } + public void ReplaceSelection(string replace) => m_TextEditing.ReplaceSelection(replace); - /// Replacted the selection with /c/ + /// Replaced the selection with /c/ public void Insert(char c) { - ReplaceSelection(c.ToString()); + m_TextEditing.Insert(c); + UpdateTextHandle(); } /// Move selection to alt cursor /position/ - public void MoveSelectionToAltCursor() - { - if (m_iAltCursorPos == -1) - return; - int p = m_iAltCursorPos; - string tmp = SelectedText; - m_Content.text = text.Insert(p, tmp); - - if (p < cursorIndex) - { - cursorIndex += tmp.Length; - selectIndex += tmp.Length; - } - - DeleteSelection(); - - selectIndex = cursorIndex = p; - ClearCursorPos(); - } + public void MoveSelectionToAltCursor() => m_TextEditing.MoveSelectionToAltCursor(); /// Move the cursor one character to the right and deselect. - public void MoveRight() - { - ClearCursorPos(); - if (selectIndex == cursorIndex) - { - cursorIndex = NextCodePointIndex(cursorIndex); - DetectFocusChange(); // TODO: Is this necessary? - selectIndex = cursorIndex; - } - else - { - if (selectIndex > cursorIndex) - cursorIndex = selectIndex; - else - selectIndex = cursorIndex; - } - } + public void MoveRight() => m_TextSelecting.MoveRight(); /// Move the cursor one character to the left and deselect. - public void MoveLeft() - { - if (selectIndex == cursorIndex) - { - cursorIndex = PreviousCodePointIndex(cursorIndex); - selectIndex = cursorIndex; - } - else - { - if (selectIndex > cursorIndex) - selectIndex = cursorIndex; - else - cursorIndex = selectIndex; - } - ClearCursorPos(); - } + public void MoveLeft() => m_TextSelecting.MoveLeft(); /// Move the cursor up and deselects. - public void MoveUp() - { - if (selectIndex < cursorIndex) - selectIndex = cursorIndex; - else - cursorIndex = selectIndex; - GrabGraphicalCursorPos(); - graphicalCursorPos.y -= 1; - cursorIndex = selectIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); - if (cursorIndex <= 0) - ClearCursorPos(); - } + public void MoveUp() => m_TextSelecting.MoveUp(); /// Move the cursor down and deselects. - public void MoveDown() - { - if (selectIndex > cursorIndex) - selectIndex = cursorIndex; - else - cursorIndex = selectIndex; - GrabGraphicalCursorPos(); - graphicalCursorPos.y += style.lineHeight + 5; - cursorIndex = selectIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); - if (cursorIndex == text.Length) - ClearCursorPos(); - } + public void MoveDown() => m_TextSelecting.MoveDown(); /// Moves the cursor to the start of the current line. - public void MoveLineStart() - { - // we start from the left-most selected character - int p = selectIndex < cursorIndex ? selectIndex : cursorIndex; - // then we scan back to find the first newline - int i = p; - while (i-- != 0) - if (text[i] == '\n') - { - selectIndex = cursorIndex = i + 1; - return; - } - selectIndex = cursorIndex = 0; - } + public void MoveLineStart() => m_TextSelecting.MoveLineStart(); /// Moves the selection to the end of the current line - public void MoveLineEnd() - { - // we start from the right-most selected character - int p = selectIndex > cursorIndex ? selectIndex : cursorIndex; - // then we scan forward to find the first newline - int i = p; - int strlen = text.Length; - while (i < strlen) - { - if (text[i] == '\n') - { - selectIndex = cursorIndex = i; - return; - } - i++; - } - selectIndex = cursorIndex = strlen; - } + public void MoveLineEnd() => m_TextSelecting.MoveLineEnd(); /// Move to the start of the current graphical line. This takes word-wrapping into consideration. - public void MoveGraphicalLineStart() - { - cursorIndex = selectIndex = GetGraphicalLineStart(cursorIndex < selectIndex ? cursorIndex : selectIndex); - } + public void MoveGraphicalLineStart() => m_TextSelecting.MoveGraphicalLineStart(); /// Move to the end of the current graphical line. This takes word-wrapping into consideration. - public void MoveGraphicalLineEnd() - { - cursorIndex = selectIndex = GetGraphicalLineEnd(cursorIndex > selectIndex ? cursorIndex : selectIndex); - } + public void MoveGraphicalLineEnd() => m_TextSelecting.MoveGraphicalLineEnd(); /// Moves the cursor to the beginning of the text - public void MoveTextStart() - { - selectIndex = cursorIndex = 0; - } + public void MoveTextStart() => m_TextSelecting.MoveTextStart(); /// Moves the cursor to the end of the text - public void MoveTextEnd() - { - selectIndex = cursorIndex = text.Length; - } - - private int IndexOfEndOfLine(int startIndex) - { - int index = text.IndexOf('\n', startIndex); - return (index != -1 ? index : text.Length); - } + public void MoveTextEnd() => m_TextSelecting.MoveTextEnd(); /// Move to the next paragraph - public void MoveParagraphForward() - { - cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; - if (cursorIndex < text.Length) - { - selectIndex = cursorIndex = IndexOfEndOfLine(cursorIndex + 1); - } - } + public void MoveParagraphForward() => m_TextSelecting.MoveParagraphForward(); /// Move to the previous paragraph - public void MoveParagraphBackward() - { - cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; - if (cursorIndex > 1) - { - selectIndex = cursorIndex = text.LastIndexOf('\n', cursorIndex - 2) + 1; - } - else - selectIndex = cursorIndex = 0; - } - - // + public void MoveParagraphBackward() => m_TextSelecting.MoveParagraphBackward(); // Move the cursor to a graphical position. Used for moving the cursor on MouseDown events. public void MoveCursorToPosition(Vector2 cursorPosition) @@ -525,256 +310,53 @@ public void MoveCursorToPosition(Vector2 cursorPosition) } // Move the cursor to a graphical position. Used for moving the cursor on MouseDown events. - protected internal void MoveCursorToPosition_Internal(Vector2 cursorPosition, bool shift) - { - selectIndex = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); - - if (!shift) - { - cursorIndex = selectIndex; - } - - DetectFocusChange(); // TODO: Is this necessary? - } + protected internal void MoveCursorToPosition_Internal(Vector2 cursorPosition, bool shift) => m_TextSelecting.MoveCursorToPosition_Internal(GetLocalCursorPosition(cursorPosition), shift); - public void MoveAltCursorToPosition(Vector2 cursorPosition) - { - int index = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); - m_iAltCursorPos = Mathf.Min(text.Length, index); - DetectFocusChange(); // TODO: Is this necessary? - } + public void MoveAltCursorToPosition(Vector2 cursorPosition) => m_TextSelecting.MoveAltCursorToPosition(GetLocalCursorPosition(cursorPosition)); - public bool IsOverSelection(Vector2 cursorPosition) - { - int p = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); - return ((p < Mathf.Max(cursorIndex, selectIndex)) && (p > Mathf.Min(cursorIndex, selectIndex))); - } + public bool IsOverSelection(Vector2 cursorPosition)=> m_TextSelecting.IsOverSelection(GetLocalCursorPosition(cursorPosition)); // Do a drag selection. Used to expand the selection in MouseDrag events. - public void SelectToPosition(Vector2 cursorPosition) - { - if (!m_MouseDragSelectsWholeWords) - cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); - else // snap to words/paragraphs - { - int p = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); - - EnsureValidCodePointIndex(ref p); - EnsureValidCodePointIndex(ref m_DblClickInitPos); - - if (m_DblClickSnap == DblClickSnapping.WORDS) - { - if (p < m_DblClickInitPos) - { - cursorIndex = FindEndOfClassification(p, Direction.Backward); - selectIndex = FindEndOfClassification(m_DblClickInitPos, Direction.Forward); - } - else - { - cursorIndex = FindEndOfClassification(p, Direction.Forward); - selectIndex = FindEndOfClassification(m_DblClickInitPos, Direction.Backward); - } - } // paragraph - else - { - if (p < m_DblClickInitPos) - { - if (p > 0) - cursorIndex = text.LastIndexOf('\n', Mathf.Max(0, p - 2)) + 1; - else - cursorIndex = 0; - - selectIndex = text.LastIndexOf('\n', Mathf.Min(text.Length - 1, m_DblClickInitPos)); - } - else - { - if (p < text.Length) - { - cursorIndex = IndexOfEndOfLine(p); - } - else - cursorIndex = text.Length; - - selectIndex = text.LastIndexOf('\n', Mathf.Max(0, m_DblClickInitPos - 2)) + 1; - } - } - } - } + public void SelectToPosition(Vector2 cursorPosition) => m_TextSelecting.SelectToPosition(GetLocalCursorPosition(cursorPosition)); - /// Expand the selection to the left - public void SelectLeft() + private Vector2 GetLocalCursorPosition(Vector2 cursorPosition) { - if (m_bJustSelected) - if (cursorIndex > selectIndex) - { // swap - int tmp = cursorIndex; - cursorIndex = selectIndex; - selectIndex = tmp; - } - m_bJustSelected = false; - - cursorIndex = PreviousCodePointIndex(cursorIndex); + return cursorPosition - style.Internal_GetTextRectOffset(position, m_Content, new Vector2(m_TextHandle.preferredSize.x, m_TextHandle.preferredSize.y > 0 ? m_TextHandle.preferredSize.y : style.lineHeight)) + scrollOffset; } - public void SelectRight() - { - if (m_bJustSelected) - if (cursorIndex < selectIndex) - { // swap - int tmp = cursorIndex; - cursorIndex = selectIndex; - selectIndex = tmp; - } - m_bJustSelected = false; + /// Expand the selection to the left + public void SelectLeft() => m_TextSelecting.SelectLeft(); - cursorIndex = NextCodePointIndex(cursorIndex); - } + public void SelectRight() => m_TextSelecting.SelectRight(); - public void SelectUp() - { - GrabGraphicalCursorPos(); - graphicalCursorPos.y -= 1; - cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); - } + public void SelectUp() => m_TextSelecting.SelectUp(); - public void SelectDown() - { - GrabGraphicalCursorPos(); - graphicalCursorPos.y += style.lineHeight + 5; - cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); - } + public void SelectDown() => m_TextSelecting.SelectDown(); /// Select to the end of the text - public void SelectTextEnd() - { - // This is not quite like the mac - there, when you select to end of text, the position of the cursor becomes somewhat i'll defined - // Hard to explain. In textedit, try: CMD-SHIFT-down, SHIFT-LEFT for case 1. then do CMD-SHIFT-down, SHIFT-RIGHT, SHIFT-LEFT for case 2. - // Anyways, it's wrong so we won't do that - cursorIndex = text.Length; - } + public void SelectTextEnd() => m_TextSelecting.SelectTextEnd(); /// Select to the start of the text - public void SelectTextStart() - { - // Same thing as SelectTextEnd... - cursorIndex = 0; - } + public void SelectTextStart() => m_TextSelecting.SelectTextStart(); /// sets whether the text selection is done by dbl click or not - public void MouseDragSelectsWholeWords(bool on) - { - m_MouseDragSelectsWholeWords = on; - m_DblClickInitPos = cursorIndex; - } + public void MouseDragSelectsWholeWords(bool on) => m_TextSelecting.MouseDragSelectsWholeWords(on); - public void DblClickSnap(DblClickSnapping snapping) - { - m_DblClickSnap = snapping; - } - - int GetGraphicalLineStart(int p) - { - Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); - point.y += 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line - point.x = 0; - return style.GetCursorStringIndex(localPosition, m_Content, point); - } - - int GetGraphicalLineEnd(int p) - { - Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); - point.y += 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line - point.x += 5000; - return style.GetCursorStringIndex(localPosition, m_Content, point); - } - - int FindNextSeperator(int startPos) - { - int textLen = text.Length; - while (startPos < textLen && ClassifyChar(startPos) != CharacterType.LetterLike) - startPos = NextCodePointIndex(startPos); - while (startPos < textLen && ClassifyChar(startPos) == CharacterType.LetterLike) - startPos = NextCodePointIndex(startPos); - return startPos; - } - - int FindPrevSeperator(int startPos) - { - startPos = PreviousCodePointIndex(startPos); - while (startPos > 0 && ClassifyChar(startPos) != CharacterType.LetterLike) - startPos = PreviousCodePointIndex(startPos); - - if (startPos == 0) - return 0; - - while (startPos > 0 && ClassifyChar(startPos) == CharacterType.LetterLike) - startPos = PreviousCodePointIndex(startPos); - - if (ClassifyChar(startPos) == CharacterType.LetterLike) - return startPos; - return NextCodePointIndex(startPos); - } + public void DblClickSnap(DblClickSnapping snapping) => m_TextSelecting.DblClickSnap(snapping); /// Move to the end of the word. /// If the cursor is over some space characters, these are skipped /// Then, the cursor moves to the end of the following word. /// This corresponds to Alt-RightArrow on a Mac - public void MoveWordRight() - { - cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; - cursorIndex = selectIndex = FindNextSeperator(cursorIndex); - ClearCursorPos(); - } - - public void MoveToStartOfNextWord() - { - ClearCursorPos(); - if (cursorIndex != selectIndex) - { - MoveRight(); - return; - } - cursorIndex = selectIndex = FindStartOfNextWord(cursorIndex); - } - - public void MoveToEndOfPreviousWord() - { - ClearCursorPos(); - if (cursorIndex != selectIndex) - { - MoveLeft(); - return; - } - cursorIndex = selectIndex = FindEndOfPreviousWord(cursorIndex); - } + public void MoveWordRight() => m_TextSelecting.MoveWordRight(); - public void SelectToStartOfNextWord() - { - ClearCursorPos(); - cursorIndex = FindStartOfNextWord(cursorIndex); - } + public void MoveToStartOfNextWord() => m_TextSelecting.MoveToStartOfNextWord(); - public void SelectToEndOfPreviousWord() - { - ClearCursorPos(); - cursorIndex = FindEndOfPreviousWord(cursorIndex); - } + public void MoveToEndOfPreviousWord() => m_TextSelecting.MoveToEndOfPreviousWord(); - enum CharacterType - { - LetterLike, - Symbol, Symbol2, - WhiteSpace - } + public void SelectToStartOfNextWord() => m_TextSelecting.SelectToStartOfNextWord(); - CharacterType ClassifyChar(int index) - { - if (char.IsWhiteSpace(text, index)) - return CharacterType.WhiteSpace; - if (char.IsLetterOrDigit(text, index) || text[index] == '\'') - return CharacterType.LetterLike; - return CharacterType.Symbol; - } + public void SelectToEndOfPreviousWord() => m_TextSelecting.SelectToEndOfPreviousWord(); /// Move to start of next word. /// This corresponds to Ctrl-RightArrow on Windows @@ -782,244 +364,39 @@ CharacterType ClassifyChar(int index) /// If the cursor is over an alphanumeric character, it''s moved forward 'till it encounters space or a punctuation mark. /// If the stopping character is a space, this is skipped as well. /// If the cursor is over an punctuation mark, it's moved forward ''till it a letter or a space of a punctuation mark. If the stopping character is a space, this is skipped as well - public int FindStartOfNextWord(int p) - { - int textLen = text.Length; - if (p == textLen) - return p; - - // Find out which char type we're at... - CharacterType t = ClassifyChar(p); - if (t != CharacterType.WhiteSpace) - { - p = NextCodePointIndex(p); - while (p < textLen && ClassifyChar(p) == t) - p = NextCodePointIndex(p); - } - else - { - if (text[p] == '\t' || text[p] == '\n') - return NextCodePointIndex(p); - } - - if (p == textLen) - return p; - - // Skip spaces - if (text[p] == ' ') // If we're at a space, skip over any number of spaces - { - while (p < textLen && ClassifyChar(p) == CharacterType.WhiteSpace) - p = NextCodePointIndex(p); - } - else if (text[p] == '\t' || text[p] == '\n') // If we're at a tab or a newline, just step one char ahead - { - return p; - } - return p; - } - - int FindEndOfPreviousWord(int p) - { - if (p == 0) - return p; - p = PreviousCodePointIndex(p); + public int FindStartOfNextWord(int p) => m_TextSelecting.FindStartOfNextWord(p); - // Skip spaces - while (p > 0 && text[p] == ' ') - p = PreviousCodePointIndex(p); + public void MoveWordLeft() => m_TextSelecting.MoveWordLeft(); - CharacterType t = ClassifyChar(p); - if (t != CharacterType.WhiteSpace) - { - while (p > 0 && ClassifyChar(PreviousCodePointIndex(p)) == t) - p = PreviousCodePointIndex(p); - } - return p; - } - - public void MoveWordLeft() - { - cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; - cursorIndex = FindPrevSeperator(cursorIndex); - selectIndex = cursorIndex; - } + public void SelectWordRight() => m_TextSelecting.SelectWordRight(); - public void SelectWordRight() - { - ClearCursorPos(); - int cachedPos = selectIndex; - if (cursorIndex < selectIndex) - { - selectIndex = cursorIndex; - MoveWordRight(); - selectIndex = cachedPos; - cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; - return; - } - selectIndex = cursorIndex; - MoveWordRight(); - selectIndex = cachedPos; - } - - public void SelectWordLeft() - { - ClearCursorPos(); - int cachedPos = selectIndex; - if (cursorIndex > selectIndex) - { - selectIndex = cursorIndex; - MoveWordLeft(); - selectIndex = cachedPos; - cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; - return; - } - selectIndex = cursorIndex; - MoveWordLeft(); - selectIndex = cachedPos; - } + public void SelectWordLeft() => m_TextSelecting.SelectWordLeft(); /// Expand the selection to the start of the line /// Used on a mac for CMD-SHIFT-LEFT - public void ExpandSelectGraphicalLineStart() - { - ClearCursorPos(); - if (cursorIndex < selectIndex) - cursorIndex = GetGraphicalLineStart(cursorIndex); - else - { - int temp = cursorIndex; - cursorIndex = GetGraphicalLineStart(selectIndex); - selectIndex = temp; - } - } + public void ExpandSelectGraphicalLineStart() => m_TextSelecting.ExpandSelectGraphicalLineStart(); /// Expand the selection to the end of the line /// Used on a mac for CMD-SHIFT-RIGHT - public void ExpandSelectGraphicalLineEnd() - { - ClearCursorPos(); - if (cursorIndex > selectIndex) - cursorIndex = GetGraphicalLineEnd(cursorIndex); - else - { - int temp = cursorIndex; - cursorIndex = GetGraphicalLineEnd(selectIndex); - selectIndex = temp; - } - } + public void ExpandSelectGraphicalLineEnd() => m_TextSelecting.ExpandSelectGraphicalLineEnd(); /// Move the selection point to the start of the line /// Used on a Windows for SHIFT-Home - public void SelectGraphicalLineStart() - { - ClearCursorPos(); - cursorIndex = GetGraphicalLineStart(cursorIndex); - } + public void SelectGraphicalLineStart() => m_TextSelecting.SelectGraphicalLineStart(); /// Expand the selection to the end of the line /// Used on a mac for SHIFT-End - public void SelectGraphicalLineEnd() - { - ClearCursorPos(); - cursorIndex = GetGraphicalLineEnd(cursorIndex); - } + public void SelectGraphicalLineEnd() => m_TextSelecting.SelectGraphicalLineEnd(); - public void SelectParagraphForward() - { - ClearCursorPos(); - bool wasBehind = cursorIndex < selectIndex; - if (cursorIndex < text.Length) - { - cursorIndex = IndexOfEndOfLine(cursorIndex + 1); - if (wasBehind && cursorIndex > selectIndex) - cursorIndex = selectIndex; - } - } + public void SelectParagraphForward() => m_TextSelecting.SelectParagraphForward(); - public void SelectParagraphBackward() - { - ClearCursorPos(); - bool wasInFront = cursorIndex > selectIndex; - if (cursorIndex > 1) - { - cursorIndex = text.LastIndexOf('\n', cursorIndex - 2) + 1; - if (wasInFront && cursorIndex < selectIndex) - cursorIndex = selectIndex; - } - else - selectIndex = cursorIndex = 0; - } + public void SelectParagraphBackward() => m_TextSelecting.SelectParagraphBackward(); /// Select the word under the cursor - public void SelectCurrentWord() - { - var index = cursorIndex; - if (cursorIndex < selectIndex) - { - cursorIndex = FindEndOfClassification(index, Direction.Backward); - selectIndex = FindEndOfClassification(index, Direction.Forward); - } - else - { - cursorIndex = FindEndOfClassification(index, Direction.Forward); - selectIndex = FindEndOfClassification(index, Direction.Backward); - } - - ClearCursorPos(); - m_bJustSelected = true; - } - - enum Direction - { - Forward, - Backward, - } - - int FindEndOfClassification(int p, Direction dir) - { - if (text.Length == 0) - return 0; - - if (p == text.Length) - p = PreviousCodePointIndex(p); - - var t = ClassifyChar(p); - do - { - switch (dir) - { - case Direction.Backward: - p = PreviousCodePointIndex(p); - if (p == 0) - return ClassifyChar(0) == t ? 0 : NextCodePointIndex(0); - break; - - case Direction.Forward: - p = NextCodePointIndex(p); - if (p == text.Length) - return text.Length; - break; - } - } - while (ClassifyChar(p) == t); - if (dir == Direction.Forward) - return p; - return NextCodePointIndex(p); - } + public void SelectCurrentWord() => m_TextSelecting.SelectCurrentWord(); // Select the entire paragraph the cursor is on (separated by \n) - public void SelectCurrentParagraph() - { - ClearCursorPos(); - int textLen = text.Length; - - if (cursorIndex < textLen) - { - cursorIndex = IndexOfEndOfLine(cursorIndex) + 1; - } - if (selectIndex != 0) - selectIndex = text.LastIndexOf('\n', selectIndex - 1) + 1; - } + public void SelectCurrentParagraph() => m_TextSelecting.SelectCurrentParagraph(); public void UpdateScrollOffsetIfNeeded(Event evt) { @@ -1029,11 +406,25 @@ public void UpdateScrollOffsetIfNeeded(Event evt) } } + internal void UpdateTextHandle() + { + m_TextHandle = IMGUITextHandle.GetTextHandle(style, style.padding.Remove(position), textWithWhitespace, Color.white, true); + m_TextEditing.textHandle = m_TextHandle; + m_TextSelecting.textHandle = m_TextHandle; + } + + Vector2 lastCursorPos = Vector2.zero; + Vector2 previousContentSize = Vector2.zero; [VisibleToOtherModules] internal void UpdateScrollOffset() { - int cursorPos = cursorIndex; - graphicalCursorPos = style.GetCursorPixelPosition(new Rect(0, 0, position.width, position.height), m_Content, cursorPos); + const int kCursorWidth = 1; + const float epsilon = 0.05f; + + var newXOffset = scrollOffset.x; + var newYOffset = scrollOffset.y; + + graphicalCursorPos = style.GetCursorPixelPosition(new Rect(0, 0, position.width, position.height), m_Content, m_TextSelecting.cursorIndexNoValidation); // The rectangle inside which the text is displayed. Rect viewRect = style.padding.Remove(position); @@ -1044,56 +435,48 @@ internal void UpdateScrollOffset() localGraphicalCursorPos.y -= style.padding.top; // The size of the text, without any padding. - Vector2 contentSize = new Vector2(style.CalcSize(m_Content).x, style.CalcHeight(m_Content, position.width)); - contentSize.x -= (style.padding.left + style.padding.right); - contentSize.y -= (style.padding.top + style.padding.bottom); + Vector2 contentSize = previousContentSize = style.GetPreferredSize(m_Content.textWithWhitespace, position); // If there is plenty of room, simply show entire string if (contentSize.x < viewRect.width) { - scrollOffset.x = 0; + newXOffset = 0; } - else if (m_RevealCursor) + else if (showCursor) { //go right - if (localGraphicalCursorPos.x + 1 > scrollOffset.x + viewRect.width) - // do we want html or apple behavior? this is html behavior - scrollOffset.x = localGraphicalCursorPos.x - viewRect.width + 1; + if (localGraphicalCursorPos.x > scrollOffset.x + viewRect.width - kCursorWidth) + newXOffset = localGraphicalCursorPos.x - viewRect.width + kCursorWidth; //go left - if (localGraphicalCursorPos.x < scrollOffset.x) - scrollOffset.x = localGraphicalCursorPos.x; + else if (localGraphicalCursorPos.x < scrollOffset.x) + newXOffset = Mathf.Max(localGraphicalCursorPos.x, 0); + //go left - applies when deleting from the string + else if (previousContentSize.x != contentSize.x && localGraphicalCursorPos.x < viewRect.x + Math.Abs(contentSize.x + kCursorWidth - viewRect.width)) + newXOffset = Mathf.Max(viewRect.width - localGraphicalCursorPos.x, 0); } + // ... and height/y as well // If there is plenty of room, simply show entire string - if (contentSize.y < viewRect.height) - { - scrollOffset.y = 0; - } - else if (m_RevealCursor) + if (contentSize.y < viewRect.height || viewRect.height == 0) + newYOffset = 0; + else if (showCursor && Math.Abs(lastCursorPos.y - localGraphicalCursorPos.y) > epsilon) { //go down if (localGraphicalCursorPos.y + style.lineHeight > scrollOffset.y + viewRect.height) - scrollOffset.y = localGraphicalCursorPos.y - viewRect.height + style.lineHeight; + newYOffset = localGraphicalCursorPos.y - viewRect.height + style.lineHeight; //go up - if (localGraphicalCursorPos.y < scrollOffset.y) - scrollOffset.y = localGraphicalCursorPos.y; + else if (localGraphicalCursorPos.y < style.lineHeight + scrollOffset.y) + newYOffset = localGraphicalCursorPos.y - style.lineHeight; } - // This case takes many words to explain: - // 1. Text field has more text than it can fit vertically, and the cursor is at the very bottom (text field is scrolled down) - // 2. user e.g. deletes some lines of text at the bottom (backspace or select+delete) - // 3. now suddenly we have space at the bottom of text field, that is now not filled with any content - // 4. scroll text field up to fill in that space (this is what other text editors do) - if (scrollOffset.y > 0 && contentSize.y - scrollOffset.y < viewRect.height) - scrollOffset.y = contentSize.y - viewRect.height; + if (scrollOffset.x != newXOffset || scrollOffset.y != newYOffset) + scrollOffset = new Vector2(newXOffset, newYOffset < 0 ? 0 : newYOffset); - scrollOffset.y = scrollOffset.y < 0 ? 0 : scrollOffset.y; - - m_RevealCursor = false; + showCursor = false; + lastCursorPos = localGraphicalCursorPos; } // TODO: get the height from the font - public void DrawCursor(string newText) { string realText = text; @@ -1106,16 +489,12 @@ public void DrawCursor(string newText) else m_Content.text = newText; - graphicalCursorPos = style.GetCursorPixelPosition(new Rect(0, 0, position.width, position.height), m_Content, cursorPos); - - //Debug.Log("Cursor pos: " + graphicalCursorPos); + graphicalCursorPos = style.GetCursorPixelPosition(position, m_Content, cursorPos); Vector2 originalContentOffset = style.contentOffset; style.contentOffset -= scrollOffset; style.Internal_clipOffset = scrollOffset; - // Debug.Log ("ScrollOffset : " + scrollOffset); - GUIUtility.compositionCursorPos = GUIClip.UnclipToWindow(graphicalCursorPos + new Vector2(position.x, position.y + style.lineHeight) - scrollOffset); if (GUIUtility.compositionString.Length > 0) @@ -1123,8 +502,8 @@ public void DrawCursor(string newText) else style.DrawWithTextSelection(position, m_Content, controlID, cursorIndex, selectIndex); - if (m_iAltCursorPos != -1) - style.DrawCursor(position, m_Content, controlID, m_iAltCursorPos); + if (m_TextSelecting.iAltCursorPos != -1) + style.DrawCursor(position, m_Content, controlID, m_TextSelecting.iAltCursorPos); // reset style.contentOffset = originalContentOffset; @@ -1133,100 +512,6 @@ public void DrawCursor(string newText) m_Content.text = realText; } - bool PerformOperation(TextEditOp operation, bool textIsReadOnly) - { - m_RevealCursor = true; - - switch (operation) - { - // NOTE the TODOs below: - case TextEditOp.MoveLeft: MoveLeft(); break; - case TextEditOp.MoveRight: MoveRight(); break; - case TextEditOp.MoveUp: MoveUp(); break; - case TextEditOp.MoveDown: MoveDown(); break; - case TextEditOp.MoveLineStart: MoveLineStart(); break; - case TextEditOp.MoveLineEnd: MoveLineEnd(); break; - case TextEditOp.MoveWordRight: MoveWordRight(); break; - case TextEditOp.MoveToStartOfNextWord: MoveToStartOfNextWord(); break; - case TextEditOp.MoveToEndOfPreviousWord: MoveToEndOfPreviousWord(); break; - case TextEditOp.MoveWordLeft: MoveWordLeft(); break; - case TextEditOp.MoveTextStart: MoveTextStart(); break; - case TextEditOp.MoveTextEnd: MoveTextEnd(); break; - case TextEditOp.MoveParagraphForward: MoveParagraphForward(); break; - case TextEditOp.MoveParagraphBackward: MoveParagraphBackward(); break; - // case TextEditOp.MovePageUp: return MovePageUp (); break; - // case TextEditOp.MovePageDown: return MovePageDown (); break; - case TextEditOp.MoveGraphicalLineStart: MoveGraphicalLineStart(); break; - case TextEditOp.MoveGraphicalLineEnd: MoveGraphicalLineEnd(); break; - case TextEditOp.SelectLeft: SelectLeft(); break; - case TextEditOp.SelectRight: SelectRight(); break; - case TextEditOp.SelectUp: SelectUp(); break; - case TextEditOp.SelectDown: SelectDown(); break; - case TextEditOp.SelectWordRight: SelectWordRight(); break; - case TextEditOp.SelectWordLeft: SelectWordLeft(); break; - case TextEditOp.SelectToEndOfPreviousWord: SelectToEndOfPreviousWord(); break; - case TextEditOp.SelectToStartOfNextWord: SelectToStartOfNextWord(); break; - - case TextEditOp.SelectTextStart: SelectTextStart(); break; - case TextEditOp.SelectTextEnd: SelectTextEnd(); break; - case TextEditOp.ExpandSelectGraphicalLineStart: ExpandSelectGraphicalLineStart(); break; - case TextEditOp.ExpandSelectGraphicalLineEnd: ExpandSelectGraphicalLineEnd(); break; - case TextEditOp.SelectParagraphForward: SelectParagraphForward(); break; - case TextEditOp.SelectParagraphBackward: SelectParagraphBackward(); break; - case TextEditOp.SelectGraphicalLineStart: SelectGraphicalLineStart(); break; - case TextEditOp.SelectGraphicalLineEnd: SelectGraphicalLineEnd(); break; - // case TextEditOp.SelectPageUp: return SelectPageUp (); break; - // case TextEditOp.SelectPageDown: return SelectPageDown (); break; - case TextEditOp.Delete: - if (textIsReadOnly) return false; - else return Delete(); - case TextEditOp.Backspace: - if (textIsReadOnly) return false; - else return Backspace(); - case TextEditOp.Cut: - if (textIsReadOnly) return false; - else return Cut(); - case TextEditOp.Copy: Copy(); break; - case TextEditOp.Paste: - if (textIsReadOnly) return false; - else return Paste(); - case TextEditOp.SelectAll: SelectAll(); break; - case TextEditOp.SelectNone: SelectNone(); break; - // case TextEditOp.ScrollStart: return ScrollStart (); break; - // case TextEditOp.ScrollEnd: return ScrollEnd (); break; - // case TextEditOp.ScrollPageUp: return ScrollPageUp (); break; - // case TextEditOp.ScrollPageDown: return ScrollPageDown (); break; - case TextEditOp.DeleteWordBack: - if (textIsReadOnly) return false; - else return DeleteWordBack(); - case TextEditOp.DeleteLineBack: - if (textIsReadOnly) return false; - else return DeleteLineBack(); - case TextEditOp.DeleteWordForward: - if (textIsReadOnly) return false; - else return DeleteWordForward(); - default: - Debug.Log("Unimplemented: " + operation); - break; - } - - return false; - } - - enum TextEditOp - { - MoveLeft, MoveRight, MoveUp, MoveDown, MoveLineStart, MoveLineEnd, MoveTextStart, MoveTextEnd, MovePageUp, MovePageDown, - MoveGraphicalLineStart, MoveGraphicalLineEnd, MoveWordLeft, MoveWordRight, - MoveParagraphForward, MoveParagraphBackward, MoveToStartOfNextWord, MoveToEndOfPreviousWord, - SelectLeft, SelectRight, SelectUp, SelectDown, SelectTextStart, SelectTextEnd, SelectPageUp, SelectPageDown, - ExpandSelectGraphicalLineStart, ExpandSelectGraphicalLineEnd, SelectGraphicalLineStart, SelectGraphicalLineEnd, - SelectWordLeft, SelectWordRight, SelectToEndOfPreviousWord, SelectToStartOfNextWord, - SelectParagraphBackward, SelectParagraphForward, - Delete, Backspace, DeleteWordBack, DeleteWordForward, DeleteLineBack, - Cut, Copy, Paste, SelectAll, SelectNone, - ScrollStart, ScrollEnd, ScrollPageUp, ScrollPageDown - } - string oldText; int oldPos, oldSelectPos; @@ -1246,185 +531,28 @@ public void Undo() public bool Cut() { - //Debug.Log ("Cut"); if (isPasswordField) return false; - Copy(); - return DeleteSelection(); + + return m_TextEditing.Cut(); } public void Copy() { - //Debug.Log ("Copy"); - if (selectIndex == cursorIndex) - return; - if (isPasswordField) return; - string selectedRenderedText = style.Internal_GetSelectedRenderedText(localPosition, m_Content, selectIndex, cursorIndex); - - GUIUtility.systemCopyBuffer = selectedRenderedText; + m_TextSelecting.Copy(); } internal Rect[] GetHyperlinksRect() { - return style.Internal_GetHyperlinksRect(localPosition, m_Content); - } - - static string ReplaceNewlinesWithSpaces(string value) - { - // First get rid of Windows style new lines and then *nix so we don't leave '\r' around. - value = value.Replace("\r\n", " "); - value = value.Replace('\n', ' '); - // This probably won't happen, but just in case... - value = value.Replace('\r', ' '); - return value; + return style.GetHyperlinkRects(m_TextHandle, localPosition); } public bool Paste() { - //Debug.Log ("Paste"); - string pasteval = GUIUtility.systemCopyBuffer; - if (pasteval != "") - { - if (!multiline) - pasteval = ReplaceNewlinesWithSpaces(pasteval); - ReplaceSelection(pasteval); - return true; - } - return false; - } - - static void MapKey(string key, TextEditOp action) - { - s_Keyactions[Event.KeyboardEvent(key)] = action; - } - - static Dictionary s_Keyactions; - /// Set up a platform independant keyboard->Edit action map. This varies depending on whether we are on mac or windows. - void InitKeyActions() - { - if (s_Keyactions != null) - return; - s_Keyactions = new Dictionary(); - - // key mappings shared by the platforms - MapKey("left", TextEditOp.MoveLeft); - MapKey("right", TextEditOp.MoveRight); - MapKey("up", TextEditOp.MoveUp); - MapKey("down", TextEditOp.MoveDown); - - MapKey("#left", TextEditOp.SelectLeft); - MapKey("#right", TextEditOp.SelectRight); - MapKey("#up", TextEditOp.SelectUp); - MapKey("#down", TextEditOp.SelectDown); - - MapKey("delete", TextEditOp.Delete); - MapKey("backspace", TextEditOp.Backspace); - MapKey("#backspace", TextEditOp.Backspace); - - // OSX is the special case for input shortcuts - if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) - { - // Keyboard mappings for mac - // TODO MapKey ("home", TextEditOp.ScrollStart); - // TODO MapKey ("end", TextEditOp.ScrollEnd); - // TODO MapKey ("page up", TextEditOp.ScrollPageUp); - // TODO MapKey ("page down", TextEditOp.ScrollPageDown); - - MapKey("^left", TextEditOp.MoveGraphicalLineStart); - MapKey("^right", TextEditOp.MoveGraphicalLineEnd); - // TODO MapKey ("^up", TextEditOp.ScrollPageUp); - // TODO MapKey ("^down", TextEditOp.ScrollPageDown); - - MapKey("&left", TextEditOp.MoveWordLeft); - MapKey("&right", TextEditOp.MoveWordRight); - MapKey("&up", TextEditOp.MoveParagraphBackward); - MapKey("&down", TextEditOp.MoveParagraphForward); - - MapKey("%left", TextEditOp.MoveGraphicalLineStart); - MapKey("%right", TextEditOp.MoveGraphicalLineEnd); - MapKey("%up", TextEditOp.MoveTextStart); - MapKey("%down", TextEditOp.MoveTextEnd); - - MapKey("#home", TextEditOp.SelectTextStart); - MapKey("#end", TextEditOp.SelectTextEnd); - // TODO MapKey ("#page up", TextEditOp.SelectPageUp); - // TODO MapKey ("#page down", TextEditOp.SelectPageDown); - - MapKey("#^left", TextEditOp.ExpandSelectGraphicalLineStart); - MapKey("#^right", TextEditOp.ExpandSelectGraphicalLineEnd); - MapKey("#^up", TextEditOp.SelectParagraphBackward); - MapKey("#^down", TextEditOp.SelectParagraphForward); - - MapKey("#&left", TextEditOp.SelectWordLeft); - MapKey("#&right", TextEditOp.SelectWordRight); - MapKey("#&up", TextEditOp.SelectParagraphBackward); - MapKey("#&down", TextEditOp.SelectParagraphForward); - - MapKey("#%left", TextEditOp.ExpandSelectGraphicalLineStart); - MapKey("#%right", TextEditOp.ExpandSelectGraphicalLineEnd); - MapKey("#%up", TextEditOp.SelectTextStart); - MapKey("#%down", TextEditOp.SelectTextEnd); - - MapKey("%a", TextEditOp.SelectAll); - MapKey("%x", TextEditOp.Cut); - MapKey("%c", TextEditOp.Copy); - MapKey("%v", TextEditOp.Paste); - - // emacs-like keybindings - MapKey("^d", TextEditOp.Delete); - MapKey("^h", TextEditOp.Backspace); - MapKey("^b", TextEditOp.MoveLeft); - MapKey("^f", TextEditOp.MoveRight); - MapKey("^a", TextEditOp.MoveLineStart); - MapKey("^e", TextEditOp.MoveLineEnd); - - MapKey("&delete", TextEditOp.DeleteWordForward); - MapKey("&backspace", TextEditOp.DeleteWordBack); - MapKey("%backspace", TextEditOp.DeleteLineBack); - } - else - { - // Windows/Linux keymappings - MapKey("home", TextEditOp.MoveGraphicalLineStart); - MapKey("end", TextEditOp.MoveGraphicalLineEnd); - // TODO MapKey ("page up", TextEditOp.MovePageUp); - // TODO MapKey ("page down", TextEditOp.MovePageDown); - - MapKey("%left", TextEditOp.MoveWordLeft); - MapKey("%right", TextEditOp.MoveWordRight); - MapKey("%up", TextEditOp.MoveParagraphBackward); - MapKey("%down", TextEditOp.MoveParagraphForward); - - MapKey("^left", TextEditOp.MoveToEndOfPreviousWord); - MapKey("^right", TextEditOp.MoveToStartOfNextWord); - MapKey("^up", TextEditOp.MoveParagraphBackward); - MapKey("^down", TextEditOp.MoveParagraphForward); - - MapKey("#^left", TextEditOp.SelectToEndOfPreviousWord); - MapKey("#^right", TextEditOp.SelectToStartOfNextWord); - MapKey("#^up", TextEditOp.SelectParagraphBackward); - MapKey("#^down", TextEditOp.SelectParagraphForward); - - MapKey("#home", TextEditOp.SelectGraphicalLineStart); - MapKey("#end", TextEditOp.SelectGraphicalLineEnd); - // TODO MapKey ("#page up", TextEditOp.SelectPageUp); - // TODO MapKey ("#page down", TextEditOp.SelectPageDown); - - MapKey("^delete", TextEditOp.DeleteWordForward); - MapKey("^backspace", TextEditOp.DeleteWordBack); - MapKey("%backspace", TextEditOp.DeleteLineBack); - - MapKey("^a", TextEditOp.SelectAll); - MapKey("^x", TextEditOp.Cut); - MapKey("^c", TextEditOp.Copy); - MapKey("^v", TextEditOp.Paste); - MapKey("#delete", TextEditOp.Cut); - MapKey("^insert", TextEditOp.Copy); - MapKey("#insert", TextEditOp.Paste); - } + return m_TextEditing.Paste(); } public void DetectFocusChange() @@ -1442,49 +570,12 @@ internal virtual void OnDetectFocusChange() internal virtual void OnCursorIndexChange() { + UpdateScrollOffset(); } internal virtual void OnSelectIndexChange() { - } - - private void ClampTextIndex(ref int index) - { - index = Mathf.Clamp(index, 0, text.Length); - } - - void EnsureValidCodePointIndex(ref int index) - { - ClampTextIndex(ref index); - if (!IsValidCodePointIndex(index)) - index = NextCodePointIndex(index); - } - - bool IsValidCodePointIndex(int index) - { - if (index < 0 || index > text.Length) - return false; - if (index == 0 || index == text.Length) - return true; - return !char.IsLowSurrogate(text[index]); - } - - int PreviousCodePointIndex(int index) - { - if (index > 0) - index--; - while (index > 0 && char.IsLowSurrogate(text[index])) - index--; - return index; - } - - int NextCodePointIndex(int index) - { - if (index < text.Length) - index++; - while (index < text.Length && char.IsLowSurrogate(text[index])) - index++; - return index; + UpdateScrollOffset(); } } } // namespace diff --git a/Modules/IMGUI/TextSelectingUtilities.cs b/Modules/IMGUI/TextSelectingUtilities.cs index f8539d614..c8824961f 100644 --- a/Modules/IMGUI/TextSelectingUtilities.cs +++ b/Modules/IMGUI/TextSelectingUtilities.cs @@ -18,7 +18,7 @@ internal class TextSelectingUtilities private bool m_bJustSelected = false; private bool m_MouseDragSelectsWholeWords = false; private int m_DblClickInitPos = 0; - TextHandle m_TextHandle; + public TextHandle textHandle; private const int kMoveDownHeight = 5; private const char kNewLineChar = '\n'; @@ -39,19 +39,33 @@ public bool revealCursor } } - int m_CharacterCount => m_TextHandle.textInfo.characterCount; - int characterCount => (m_CharacterCount > 0 && m_TextHandle.textInfo.textElementInfo[m_CharacterCount - 1].character == 0x200B) ? m_CharacterCount - 1 : m_CharacterCount; - TextElementInfo[] m_TextElementInfos => m_TextHandle.textInfo.textElementInfo; + int m_CharacterCount => textHandle.textInfo.characterCount; + int characterCount => (m_CharacterCount > 0 && textHandle.textInfo.textElementInfo[m_CharacterCount - 1].character == 0x200B) ? m_CharacterCount - 1 : m_CharacterCount; + TextElementInfo[] m_TextElementInfos => textHandle.textInfo.textElementInfo; int m_CursorIndex = 0; public int cursorIndex { - get => m_TextHandle.IsPlaceholder ? 0 : EnsureValidCodePointIndex(m_CursorIndex); + get => textHandle.IsPlaceholder ? 0 : EnsureValidCodePointIndex(m_CursorIndex); + set + { + if (m_CursorIndex != value) + { + m_CursorIndex = value; + revealCursor = true; + OnCursorIndexChange?.Invoke(); + } + } + } + internal int cursorIndexNoValidation + { + get { return m_CursorIndex; } set { if (m_CursorIndex != value) { SetCursorIndexWithoutNotify(value); + revealCursor = true; OnCursorIndexChange?.Invoke(); } } @@ -65,7 +79,7 @@ internal void SetCursorIndexWithoutNotify(int index) internal int m_SelectIndex = 0; public int selectIndex { - get => m_TextHandle.IsPlaceholder ? 0 : EnsureValidCodePointIndex(m_SelectIndex); + get => textHandle.IsPlaceholder ? 0 : EnsureValidCodePointIndex(m_SelectIndex); set { @@ -90,16 +104,16 @@ public string selectedText return ""; if (cursorIndex < selectIndex) - return m_TextHandle.Substring(cursorIndex, selectIndex - cursorIndex); + return textHandle.Substring(cursorIndex, selectIndex - cursorIndex); else - return m_TextHandle.Substring(selectIndex, cursorIndex - selectIndex); + return textHandle.Substring(selectIndex, cursorIndex - selectIndex); } } public TextSelectingUtilities(TextHandle textHandle) { - m_TextHandle = textHandle; + this.textHandle = textHandle; } internal bool HandleKeyEvent(Event e) @@ -269,12 +283,12 @@ public void SelectRight() public void SelectUp() { - cursorIndex = m_TextHandle.LineUpCharacterPosition(cursorIndex); + cursorIndex = textHandle.LineUpCharacterPosition(cursorIndex); } public void SelectDown() { - cursorIndex = m_TextHandle.LineDownCharacterPosition(cursorIndex); + cursorIndex = textHandle.LineDownCharacterPosition(cursorIndex); } /// Select to the end of the text @@ -373,7 +387,7 @@ public void SelectParagraphBackward() bool wasInFront = cursorIndex > selectIndex; if (cursorIndex > 1) { - cursorIndex = m_TextHandle.LastIndexOf(kNewLineChar, cursorIndex - 2) + 1; + cursorIndex = textHandle.LastIndexOf(kNewLineChar, cursorIndex - 2) + 1; if (wasInFront && cursorIndex < selectIndex) cursorIndex = selectIndex; } @@ -409,7 +423,7 @@ public void SelectCurrentParagraph() if (cursorIndex < textLen) cursorIndex = IndexOfEndOfLine(cursorIndex); if (selectIndex != 0) - selectIndex = m_TextHandle.LastIndexOf(kNewLineChar, selectIndex - 1) + 1; + selectIndex = textHandle.LastIndexOf(kNewLineChar, selectIndex - 1) + 1; } /// Move the cursor one character to the right and deselect. @@ -455,7 +469,7 @@ public void MoveUp() selectIndex = cursorIndex; else cursorIndex = selectIndex; - cursorIndex = selectIndex = m_TextHandle.LineUpCharacterPosition(cursorIndex); + cursorIndex = selectIndex = textHandle.LineUpCharacterPosition(cursorIndex); if (cursorIndex <= 0) ClearCursorPos(); } @@ -467,7 +481,7 @@ public void MoveDown() selectIndex = cursorIndex; else cursorIndex = selectIndex; - cursorIndex = selectIndex = m_TextHandle.LineDownCharacterPosition(cursorIndex); + cursorIndex = selectIndex = textHandle.LineDownCharacterPosition(cursorIndex); if (cursorIndex == characterCount) ClearCursorPos(); } @@ -548,7 +562,7 @@ public void MoveParagraphBackward() cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; if (cursorIndex > 1) { - selectIndex = cursorIndex = m_TextHandle.LastIndexOf(kNewLineChar, cursorIndex - 2) + 1; + selectIndex = cursorIndex = textHandle.LastIndexOf(kNewLineChar, cursorIndex - 2) + 1; } else selectIndex = cursorIndex = 0; @@ -638,7 +652,7 @@ public void DblClickSnap(DblClickSnapping snapping) protected internal void MoveCursorToPosition_Internal(Vector2 cursorPosition, bool shift) { - selectIndex = m_TextHandle.GetCursorIndexFromPosition(cursorPosition); + selectIndex = textHandle.GetCursorIndexFromPosition(cursorPosition); if (!shift) { @@ -646,15 +660,26 @@ protected internal void MoveCursorToPosition_Internal(Vector2 cursorPosition, bo } } + protected internal void MoveAltCursorToPosition(Vector2 cursorPosition) + { + int index = textHandle.GetCursorIndexFromPosition(cursorPosition); + iAltCursorPos = Mathf.Min(characterCount, index); + } + + protected internal bool IsOverSelection(Vector2 cursorPosition) + { + int p = textHandle.GetCursorIndexFromPosition(cursorPosition); + return ((p < Mathf.Max(cursorIndex, selectIndex)) && (p > Mathf.Min(cursorIndex, selectIndex))); + } + // Do a drag selection. Used to expand the selection in MouseDrag events. public void SelectToPosition(Vector2 cursorPosition) { if (!m_MouseDragSelectsWholeWords) - cursorIndex = m_TextHandle.GetCursorIndexFromPosition(cursorPosition); + cursorIndex = textHandle.GetCursorIndexFromPosition(cursorPosition); else // snap to words/paragraphs { - int p = m_TextHandle.GetCursorIndexFromPosition(cursorPosition); - + int p = textHandle.GetCursorIndexFromPosition(cursorPosition); p = EnsureValidCodePointIndex(p); m_DblClickInitPos = EnsureValidCodePointIndex(m_DblClickInitPos); @@ -676,11 +701,11 @@ public void SelectToPosition(Vector2 cursorPosition) if (p < m_DblClickInitPos) { if (p > 0) - cursorIndex = m_TextHandle.LastIndexOf(kNewLineChar, Mathf.Max(0, p - 2)) + 1; + cursorIndex = textHandle.LastIndexOf(kNewLineChar, Mathf.Max(0, p - 2)) + 1; else cursorIndex = 0; - selectIndex = m_TextHandle.LastIndexOf(kNewLineChar, Mathf.Min(characterCount - 1, m_DblClickInitPos)); + selectIndex = textHandle.LastIndexOf(kNewLineChar, Mathf.Min(characterCount - 1, m_DblClickInitPos)); } else { @@ -691,7 +716,7 @@ public void SelectToPosition(Vector2 cursorPosition) else cursorIndex = characterCount; - selectIndex = m_TextHandle.LastIndexOf(kNewLineChar, Mathf.Max(0, m_DblClickInitPos - 2)) + 1; + selectIndex = textHandle.LastIndexOf(kNewLineChar, Mathf.Max(0, m_DblClickInitPos - 2)) + 1; } } } @@ -817,11 +842,6 @@ int FindEndOfClassification(int p, Direction dir) internal Action OnSelectIndexChange; internal Action OnRevealCursorChange; - int ClampTextIndex(int index) - { - return Mathf.Clamp(index, 0, characterCount); - } - internal int EnsureValidCodePointIndex(int index) { index = ClampTextIndex(index); @@ -839,9 +859,14 @@ bool IsValidCodePointIndex(int index) return !char.IsLowSurrogate(m_TextElementInfos[index].character); } + int ClampTextIndex(int index) + { + return Mathf.Clamp(index, 0, characterCount); + } + int IndexOfEndOfLine(int startIndex) { - int index = m_TextHandle.IndexOf(kNewLineChar, startIndex); + int index = textHandle.IndexOf(kNewLineChar, startIndex); return (index != -1 ? index : characterCount); } @@ -851,6 +876,7 @@ public int PreviousCodePointIndex(int index) index--; while (index > 0 && char.IsLowSurrogate(m_TextElementInfos[index].character)) index--; + return index; } @@ -860,23 +886,24 @@ public int NextCodePointIndex(int index) index++; while (index < characterCount && char.IsLowSurrogate(m_TextElementInfos[index].character)) index++; + return index; } int GetGraphicalLineStart(int p) { - Vector2 point = m_TextHandle.GetCursorPositionFromStringIndexUsingLineHeight(p); + Vector2 point = textHandle.GetCursorPositionFromStringIndexUsingLineHeight(p); point.y -= 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line point.x = 0; - return m_TextHandle.GetCursorIndexFromPosition(point); + return textHandle.GetCursorIndexFromPosition(point); } int GetGraphicalLineEnd(int p) { - Vector2 point = m_TextHandle.GetCursorPositionFromStringIndexUsingLineHeight(p); + Vector2 point = textHandle.GetCursorPositionFromStringIndexUsingLineHeight(p); point.y -= 1.0f / GUIUtility.pixelsPerPoint; // we make sure no floating point errors can make us land on another line point.x += 5000; - return m_TextHandle.GetCursorIndexFromPosition(point); + return textHandle.GetCursorIndexFromPosition(point); } public void Copy() diff --git a/Modules/PackageManager/Editor/Managed/RequestProgress.cs b/Modules/PackageManager/Editor/Managed/RequestProgress.cs index 0cea9c273..db68dff57 100644 --- a/Modules/PackageManager/Editor/Managed/RequestProgress.cs +++ b/Modules/PackageManager/Editor/Managed/RequestProgress.cs @@ -4,6 +4,7 @@ using System; using UnityEngine.Bindings; +using RequiredByNativeCodeAttribute = UnityEngine.Scripting.RequiredByNativeCodeAttribute; namespace UnityEditor.PackageManager { @@ -11,11 +12,9 @@ namespace UnityEditor.PackageManager [StaticAccessor("PackageManager::Api", StaticAccessorType.DoubleColon)] internal class RequestProgress { - private protected delegate void RequestProgressCallback(IntPtr progressUpdatesPtr); + private static extern void SetProgressHandler(long operationId, RequestProgress requestProgress); - private protected static extern void SetProgressDelegate(long operationId, RequestProgressCallback progressDelegate); - - private protected static extern void ClearProgressDelegate(long operationId); + private static extern void ClearProgressHandler(long operationId); private Action m_ProgressUpdate; @@ -34,7 +33,7 @@ public event Action progressUpdated { if (m_ProgressUpdate == null) { - SetProgressDelegate(m_OperationId, OnNativeProgress); + SetProgressHandler(m_OperationId, this); } m_ProgressUpdate += value; @@ -49,7 +48,7 @@ public event Action progressUpdated if (m_ProgressUpdate == null) { - ClearProgressDelegate(m_OperationId); + ClearProgressHandler(m_OperationId); } } } @@ -59,11 +58,12 @@ public event Action progressUpdated [StaticAccessor("PackageManager::PackageProgress", StaticAccessorType.DoubleColon)] private static extern PackageProgress[] Internal_GetPackageProgressArray(IntPtr nativeHandle); - private void OnNativeProgress(IntPtr progressUpdatesPtr) + [RequiredByNativeCode] + internal static void OnNativeProgress(RequestProgress requestProgress, IntPtr progressUpdatesPtr) { var progressUpdates = Internal_GetPackageProgressArray(progressUpdatesPtr); - InvokeProgressUpdated(new ProgressUpdateEventArgs(progressUpdates)); + requestProgress.InvokeProgressUpdated(new ProgressUpdateEventArgs(progressUpdates)); } private void InvokeProgressUpdated(ProgressUpdateEventArgs e) diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/Asset.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/Asset.cs new file mode 100644 index 000000000..913015fc9 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/Asset.cs @@ -0,0 +1,17 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace UnityEditor.PackageManager.UI.Internal +{ + [Serializable] + internal class Asset + { + public string guid; + public AssetOrigin origin; + + public string importedPath; + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetSelectionHandler.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetSelectionHandler.cs new file mode 100644 index 000000000..a5cb0f90e --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetSelectionHandler.cs @@ -0,0 +1,40 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace UnityEditor.PackageManager.UI.Internal; + +internal class AssetSelectionHandler +{ + public event Action> onRemoveSelectionDone = delegate {}; + + private SelectionWindowProxy m_SelectionWindowProxy; + + public void ResolveDependencies(SelectionWindowProxy selectionWindowProxy) + { + m_SelectionWindowProxy = selectionWindowProxy; + } + + public void OnEnable() + { + m_SelectionWindowProxy.onRemoveSelectionDone += OnRemoveSelectionDone; + } + + public void OnDisable() + { + m_SelectionWindowProxy.onRemoveSelectionDone -= OnRemoveSelectionDone; + } + + internal virtual void Remove(IEnumerable assets, string packageName, string versionString) + { + m_SelectionWindowProxy.Open(new SelectionWindowData(assets, packageName, versionString)); + } + + private void OnRemoveSelectionDone(IEnumerable selections) + { + onRemoveSelectionDone?.Invoke(selections); + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs index f8dd47572..0d3bcfe18 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreCache.cs @@ -22,6 +22,10 @@ internal class AssetStoreCache : ISerializationCallbackReceiver private Dictionary m_UpdateInfos = new Dictionary(); + // We use the guid string as the key for each imported asset + private Dictionary m_ImportedAssets = new Dictionary(); + private Dictionary m_ImportedPackages = new Dictionary(); + [SerializeField] private string[] m_SerializedCategories = new string[0]; @@ -40,13 +44,19 @@ internal class AssetStoreCache : ISerializationCallbackReceiver [SerializeField] private AssetStoreUpdateInfo[] m_SerializedUpdateInfos = new AssetStoreUpdateInfo[0]; + [SerializeField] + private Asset[] m_SerializedImportedAssets = new Asset[0]; + public virtual event Action /*addedOrUpdated*/, IEnumerable /*removed*/> onLocalInfosChanged; public virtual event Action onProductInfoChanged; public virtual event Action> onPurchaseInfosChanged; public virtual event Action> onUpdateInfosChanged; + public virtual event Action /*addedOrUpdated*/, IEnumerable /*removed*/> onImportedPackagesChanged; public virtual IEnumerable localInfos => m_LocalInfos.Values; + public virtual IEnumerable importedPackages => m_ImportedPackages.Values; + [NonSerialized] private ApplicationProxy m_Application; [NonSerialized] @@ -75,6 +85,8 @@ public void OnBeforeSerialize() m_SerializedProductInfos = m_ProductInfos.Values.ToArray(); m_SerializedLocalInfos = m_LocalInfos.Values.ToArray(); m_SerializedUpdateInfos = m_UpdateInfos.Values.ToArray(); + + m_SerializedImportedAssets = m_ImportedAssets.Values.ToArray(); } public void OnAfterDeserialize() @@ -86,6 +98,20 @@ public void OnAfterDeserialize() m_ProductInfos = m_SerializedProductInfos.ToDictionary(info => info.productId, info => info); m_LocalInfos = m_SerializedLocalInfos.ToDictionary(info => info.productId, info => info); m_UpdateInfos = m_SerializedUpdateInfos.ToDictionary(info => info.productId, info => info); + + m_ImportedAssets = m_SerializedImportedAssets.ToDictionary(asset => asset.guid, asset => asset); + + // We don't serialize imported packages, because the list of imported packages can be constructed from imported assets + foreach (var asset in m_SerializedImportedAssets) + { + if (m_ImportedPackages.TryGetValue(asset.origin.productId, out var importedPackage)) + { + importedPackage.AddImportedAsset(asset); + continue; + } + + m_ImportedPackages[asset.origin.productId] = new AssetStoreImportedPackage(new List() { asset }); + } } public virtual void SetCategory(string category, long count) @@ -192,6 +218,11 @@ public virtual AssetStoreUpdateInfo GetUpdateInfo(long? productId) return productId > 0 ? m_UpdateInfos.Get(productId.Value) : null; } + public virtual AssetStoreImportedPackage GetImportedPackage(long? productId) + { + return productId > 0 ? m_ImportedPackages.Get(productId.Value) : null; + } + public virtual void SetPurchaseInfos(IEnumerable purchaseInfos) { var updatedPurcahseInfos = new List(); @@ -278,5 +309,64 @@ public virtual void SetUpdateInfos(IEnumerable updateInfos if (updateInfosChanged.Any()) onUpdateInfosChanged?.Invoke(updateInfosChanged); } + + public virtual void SetImportedAssets(IEnumerable importedAssets) + { + var oldImportedAssets = m_ImportedAssets; + m_ImportedAssets = importedAssets.ToDictionary(asset => asset.guid, asset => asset); + + var modifiedProductIds = new HashSet(); + foreach (var asset in m_ImportedAssets.Values) + { + var oldAsset = oldImportedAssets.Get(asset.guid); + if (oldAsset != null) + oldImportedAssets.Remove(asset.guid); + if (oldAsset == null || !oldAsset.Equals(asset)) + modifiedProductIds.Add(asset.origin.productId); + } + + foreach (var asset in oldImportedAssets.Values) + modifiedProductIds.Add(asset.origin.productId); + + if (modifiedProductIds.Any()) + RefreshImportedPackageList(modifiedProductIds); + } + + private void RefreshImportedPackageList(HashSet modifiedProductIds) + { + var addedOrUpdatedPackages = new Dictionary(); + foreach (var asset in m_ImportedAssets.Values) + { + var productId = asset.origin.productId; + if (!modifiedProductIds.Contains(productId)) + continue; + + if (addedOrUpdatedPackages.TryGetValue(asset.origin.productId, out var package)) + { + package.AddImportedAsset(asset); + continue; + } + addedOrUpdatedPackages[asset.origin.productId] = new AssetStoreImportedPackage(new List() { asset }); + } + + var removedPackages = new List(); + foreach (var productId in modifiedProductIds) + { + if (addedOrUpdatedPackages.TryGetValue(productId, out var package)) + { + m_ImportedPackages[productId] = package; + continue; + } + + if (m_ImportedPackages.TryGetValue(productId, out var removedPackage)) + { + m_ImportedPackages.Remove(productId); + removedPackages.Add(removedPackage); + } + } + + if (addedOrUpdatedPackages.Any() || removedPackages.Any()) + onImportedPackagesChanged?.Invoke(addedOrUpdatedPackages.Values, removedPackages); + } } } diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClientV2.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClientV2.cs index 397514156..5fc11add0 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClientV2.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreClientV2.cs @@ -33,13 +33,13 @@ internal class AssetStoreClientV2 [NonSerialized] private FetchStatusTracker m_FetchStatusTracker; [NonSerialized] - private UpmCache m_UpmCache; + private AssetDatabaseProxy m_AssetDatabase; public void ResolveDependencies(UnityConnectProxy unityConnect, AssetStoreCache assetStoreCache, AssetStoreUtils assetStoreUtils, AssetStoreRestAPI assetStoreRestAPI, FetchStatusTracker fetchStatusTracker, - UpmCache upmCache) + AssetDatabaseProxy assetDatabase) { m_UnityConnect = unityConnect; m_AssetStoreCache = assetStoreCache; @@ -47,7 +47,7 @@ public void ResolveDependencies(UnityConnectProxy unityConnect, m_AssetStoreRestAPI = assetStoreRestAPI; m_FetchStatusTracker = fetchStatusTracker; - m_UpmCache = upmCache; + m_AssetDatabase = assetDatabase; m_ListOperation?.ResolveDependencies(unityConnect, assetStoreRestAPI, assetStoreCache); } @@ -175,5 +175,32 @@ public virtual void FetchUpdateInfos(IEnumerable productIds, Action doneCa doneCallback?.Invoke(); }); } + + public virtual IEnumerable ListImportedAssets() + { + // We need to manually create the SearchFilter so that we look for assetorigins + var filter = new SearchFilter { searchArea = SearchFilter.SearchArea.AllAssets }; + filter.ClearSearch(); + filter.originalText = "assetorigin:"; + filter.anyWithAssetOrigin = true; + + var guidsWithOrigin = m_AssetDatabase.FindAssets(filter); + return guidsWithOrigin.Select(guid => + { + var assetOrigin = m_AssetDatabase.GetAssetOrigin(guid); + var assetPath = m_AssetDatabase.GUIDToAssetPath(guid); + return new Asset + { + guid = guid, + importedPath = assetPath, + origin = assetOrigin + }; + }); + } + + public virtual void RefreshImportedAssets() + { + m_AssetStoreCache.SetImportedAssets(ListImportedAssets()); + } } } diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreImportedPackage.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreImportedPackage.cs new file mode 100644 index 000000000..212cf2f2a --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreImportedPackage.cs @@ -0,0 +1,69 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityEditor.PackageManager.UI.Internal +{ + [Serializable] + internal class AssetStoreImportedPackage : IEnumerable + { + public long productId => latestAssetOrigin?.productId ?? 0; + public string displayName => latestAssetOrigin?.packageName ?? ""; + public string versionString => latestAssetOrigin?.packageVersion ?? ""; + public long uploadId => latestAssetOrigin?.uploadId ?? 0; + + // Used in UI. Highest/latest asset origin found amongst importedAssets. This is set + // as part of the List function + [SerializeField] + private AssetOrigin m_LatestAssetOrigin; + public AssetOrigin latestAssetOrigin + { + get + { + if (m_LatestAssetOrigin == null) + CalculateLatestAssetOrigin(); + return m_LatestAssetOrigin; + } + } + + [SerializeField] + private List m_ImportedAssets; + + public AssetStoreImportedPackage(List importedAssets) + { + m_ImportedAssets = importedAssets ?? new List(); + CalculateLatestAssetOrigin(); + } + + private void CalculateLatestAssetOrigin() + { + m_LatestAssetOrigin = m_ImportedAssets.OrderByDescending(x => x.origin.uploadId).FirstOrDefault().origin; + } + + public void AddImportedAsset(Asset importedAsset) + { + if (importedAsset.origin == null) + return; + + m_ImportedAssets.Add(importedAsset); + if (importedAsset.origin.uploadId > m_LatestAssetOrigin.uploadId) + m_LatestAssetOrigin = importedAsset.origin; + } + + public IEnumerator GetEnumerator() + { + return m_ImportedAssets.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_ImportedAssets.GetEnumerator(); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreListOperation.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreListOperation.cs index b3c40fec7..b54facfca 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreListOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreListOperation.cs @@ -16,6 +16,7 @@ internal class AssetStoreListOperation : IOperation private static readonly string k_UserNotLoggedInErrorMessage = L10n.Tr("User not logged in."); public string packageUniqueId => string.Empty; + public long productId => 0; [SerializeField] protected long m_Timestamp; @@ -51,7 +52,8 @@ internal class AssetStoreListOperation : IOperation [SerializeField] private bool m_DownloadedAssetsOnly; - + [SerializeField] + private bool m_ImportedAssetsOnly; [SerializeField] private bool m_UpdateAvailableOnly; @@ -117,7 +119,7 @@ public void Start(PurchasesQueryArgs queryArgs) } m_Result = new AssetStorePurchases(m_OriginalQueryArgs); - if ((m_DownloadedAssetsOnly || m_UpdateAvailableOnly) && !m_AdjustedQueryArgs.productIds.Any()) + if ((m_DownloadedAssetsOnly || m_UpdateAvailableOnly || m_ImportedAssetsOnly) && !m_AdjustedQueryArgs.productIds.Any()) { m_Result.total = 0; onOperationSuccess?.Invoke(this); @@ -142,6 +144,7 @@ private void SetQueryArgs(PurchasesQueryArgs queryArgs) m_OriginalQueryArgs = queryArgs; m_DownloadedAssetsOnly = m_OriginalQueryArgs.downloadedOnly; + m_ImportedAssetsOnly = m_OriginalQueryArgs.importedOnly; m_UpdateAvailableOnly = m_OriginalQueryArgs.updateAvailableOnly; // The GetPurchases API has a limit of maximum 1000 items (to avoid performance issues) // therefore we do some adjustments to the original query args enforce that limit and split @@ -154,6 +157,11 @@ private void SetQueryArgs(PurchasesQueryArgs queryArgs) m_AdjustedQueryArgs.status = string.Empty; m_AdjustedQueryArgs.productIds = m_AssetStoreCache.localInfos.Select(info => info.productId).ToList(); } + else if (m_ImportedAssetsOnly) + { + m_AdjustedQueryArgs.status = string.Empty; + m_AdjustedQueryArgs.productIds = m_AssetStoreCache.importedPackages.Select(p => p.productId).ToList(); + } else if (m_UpdateAvailableOnly) { m_AdjustedQueryArgs.status = string.Empty; diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs index 7e0f13b9f..c64399edf 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreLocalInfo.cs @@ -13,6 +13,7 @@ internal class AssetStoreLocalInfo public long productId; public long uploadId; public long versionId; + public string title; public string versionString; public string publishedDate; public string supportedVersion; @@ -36,6 +37,7 @@ public static AssetStoreLocalInfo ParseLocalInfo(UnityEditor.PackageInfo localIn { productId = productId, packagePath = localInfo.packagePath ?? string.Empty, + title = jsonInfo.GetString("title") ?? string.Empty, versionString = jsonInfo.GetString("version") ?? string.Empty, versionId = jsonInfo.GetStringAsLong("version_id"), uploadId = jsonInfo.GetStringAsLong("upload_id"), diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageFactory.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageFactory.cs index 8ccb9a6a9..8a02b1aa8 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageFactory.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageFactory.cs @@ -53,6 +53,7 @@ public void OnEnable() m_AssetStoreCache.onPurchaseInfosChanged += OnPurchaseInfosChanged; m_AssetStoreCache.onProductInfoChanged += OnProductInfoChanged; m_AssetStoreCache.onUpdateInfosChanged += OnUpdateInfosChanged; + m_AssetStoreCache.onImportedPackagesChanged += OnImportedPackagesChanged; m_AssetStoreDownloadManager.onDownloadProgress += OnDownloadProgress; m_AssetStoreDownloadManager.onDownloadFinalized += OnDownloadFinalized; @@ -71,6 +72,7 @@ public void OnDisable() m_AssetStoreCache.onPurchaseInfosChanged -= OnPurchaseInfosChanged; m_AssetStoreCache.onProductInfoChanged -= OnProductInfoChanged; m_AssetStoreCache.onUpdateInfosChanged -= OnUpdateInfosChanged; + m_AssetStoreCache.onImportedPackagesChanged -= OnImportedPackagesChanged; m_AssetStoreDownloadManager.onDownloadProgress -= OnDownloadProgress; m_AssetStoreDownloadManager.onDownloadFinalized -= OnDownloadFinalized; @@ -189,6 +191,14 @@ private void OnLocalInfosChanged(IEnumerable addedOrUpdated GeneratePackagesAndTriggerChangeEvent(productIds); } + private void OnImportedPackagesChanged(IEnumerable addedOrUpdated, IEnumerable removed) + { + // Since users could have way more locally downloaded .unitypackages than what's in their purchase list + // we don't want to trigger change events for all of them, only the ones we already checked before (the ones with productInfos) + var productIds = addedOrUpdated?.Select(info => info.productId).Concat(removed.Select(info => info.productId) ?? new long[0]); + GeneratePackagesAndTriggerChangeEvent(productIds); + } + private void OnUpdateInfosChanged(IEnumerable updateInfos) { // Right now updateInfo goes hands in hands with localInfo, so we handle it the same way as localInfo changes @@ -243,7 +253,8 @@ public void GeneratePackagesAndTriggerChangeEvent(IEnumerable productIds) var isDeprecated = productInfo.state.Equals("deprecated", StringComparison.InvariantCultureIgnoreCase); var localInfo = m_AssetStoreCache.GetLocalInfo(productId); var updateInfo = m_AssetStoreCache.GetUpdateInfo(productId); - var versionList = new AssetStoreVersionList(m_IOProxy, productInfo, localInfo, updateInfo); + var importedPackage = m_AssetStoreCache.GetImportedPackage(productId); + var versionList = new AssetStoreVersionList(m_IOProxy, productInfo, localInfo, updateInfo, importedPackage); var package = CreatePackage(string.Empty, versionList, new Product(productId, purchaseInfo, productInfo), isDeprecated: isDeprecated); if (m_AssetStoreDownloadManager.GetDownloadOperation(productId)?.isInProgress == true) SetProgress(package, PackageProgress.Downloading); diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageInstaller.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageInstaller.cs new file mode 100644 index 000000000..ca3ab5693 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageInstaller.cs @@ -0,0 +1,141 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityEditor.PackageManager.UI.Internal +{ + internal class AssetStorePackageInstaller + { + [NonSerialized] + private IOProxy m_IOProxy; + [NonSerialized] + private AssetStoreCache m_AssetStoreCache; + [NonSerialized] + private AssetDatabaseProxy m_AssetDatabase; + [NonSerialized] + private AssetSelectionHandler m_AssetSelectionHandler; + public void ResolveDependencies(IOProxy ioProxy, + AssetStoreCache assetStoreCache, + AssetDatabaseProxy assetDatabaseProxy, + AssetSelectionHandler assetSelectionHandler) + { + m_IOProxy = ioProxy; + m_AssetStoreCache = assetStoreCache; + m_AssetDatabase = assetDatabaseProxy; + m_AssetSelectionHandler = assetSelectionHandler; + } + + public void OnEnable() + { + m_AssetSelectionHandler.onRemoveSelectionDone += OnRemoveSelectionDone; + } + + public void OnDisable() + { + m_AssetSelectionHandler.onRemoveSelectionDone -= OnRemoveSelectionDone; + } + + public virtual void Install(long productId, bool interactiveInstall = false) + { + var localInfo = m_AssetStoreCache.GetLocalInfo(productId); + if (localInfo == null) + return; + + var assetOrigin = new AssetOrigin((int)localInfo.productId, localInfo.title, localInfo.versionString, (int)localInfo.uploadId); + m_AssetDatabase.ImportPackage(localInfo.packagePath, assetOrigin, interactiveInstall); + } + + private void RemoveAssetsAndCleanUpEmptyFolders(IEnumerable assets) + { + const string assetsPath = "Assets"; + var foldersToRemove = new HashSet(); + foreach (var asset in assets) + { + var path = m_IOProxy.GetParentDirectory(asset.importedPath); + // We want to add an asset's parent folders all the way up to the `Assets` folder, because we don't want to leave behind + // empty folders after the assets are removed process. + while (!foldersToRemove.Contains(path) && path.StartsWith(assetsPath) && path.Length > assetsPath.Length) + { + foldersToRemove.Add(path); + path = m_IOProxy.GetParentDirectory(path); + } + } + var searchInFoldersFilter = new SearchFilter + { + folders = foldersToRemove.ToArray(), + searchArea = SearchFilter.SearchArea.SelectedFolders + }; + + var leftOverAssetsGuids = m_AssetDatabase.FindAssets(searchInFoldersFilter).ToHashSet(); + foreach (var guid in assets.Select(i => i.guid).Concat(foldersToRemove.Select(i => m_AssetDatabase.AssetPathToGUID(i)))) + leftOverAssetsGuids.Remove(guid); + + foreach (var assetPath in leftOverAssetsGuids.Select(i => m_AssetDatabase.GUIDToAssetPath(i))) + { + var path = m_IOProxy.GetParentDirectory(assetPath); + // If after the removal process, there will still be some assets left behind, we want to make sure the folders containing + // left over assets are not removed + while (foldersToRemove.Contains(path)) + { + foldersToRemove.Remove(path); + path = m_IOProxy.GetParentDirectory(path); + } + } + + // We order the folders to be removed so that child folders always come before their parent folders + // This way m_AssetDatabase.DeleteAssets call won't try to remove parent folders first and fail to remove child folders + var orderedFoldersToRemove = foldersToRemove.OrderByDescending(i => i); + var assetAndFoldersToRemove = assets.Select(i => i.importedPath).Concat(orderedFoldersToRemove).ToArray(); + var pathsFailedToRemove = new List(); + m_AssetDatabase.DeleteAssets(assetAndFoldersToRemove, pathsFailedToRemove); + + if (pathsFailedToRemove.Any()) + { + var errorMessage = L10n.Tr("[Package Manager Window] Failed to remove the following asset(s) and/or folder(s):"); + foreach (var path in pathsFailedToRemove) + errorMessage += "\n" + path; + Debug.LogError(errorMessage); + } + } + + public virtual void Uninstall(long productId, bool interactiveUninstall = false) + { + if (interactiveUninstall) + { + var importedPackage = m_AssetStoreCache.GetImportedPackage(productId); + m_AssetSelectionHandler.Remove(importedPackage, importedPackage.displayName, importedPackage.versionString); + } + else + { + var importedPackage = m_AssetStoreCache.GetImportedPackage(productId); + if (importedPackage != null) + RemoveAssetsAndCleanUpEmptyFolders(importedPackage); + } + } + + public virtual void Uninstall(IEnumerable productIds) + { + var assetsToRemove = new List(); + foreach (var productId in productIds ?? Enumerable.Empty()) + { + var importedPackage = m_AssetStoreCache.GetImportedPackage(productId); + if (importedPackage != null) + assetsToRemove.AddRange(importedPackage); + } + + if (assetsToRemove.Any()) + RemoveAssetsAndCleanUpEmptyFolders(assetsToRemove); + } + + private void OnRemoveSelectionDone(IEnumerable selections) + { + if (selections.Any()) + RemoveAssetsAndCleanUpEmptyFolders(selections); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs index 45203b948..7cf4970a8 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStorePackageVersion.cs @@ -31,6 +31,8 @@ internal class AssetStorePackageVersion : BasePackageVersion, ISerializationCall private long m_VersionId; [SerializeField] private List m_SupportedUnityVersions; + [SerializeField] + private AssetStoreImportedPackage m_ImportedPackage; [SerializeField] private string m_SupportedUnityVersionString; @@ -71,6 +73,8 @@ internal class AssetStorePackageVersion : BasePackageVersion, ISerializationCall public override IEnumerable sizes => m_SizeInfos; + public override IEnumerable importedAssets => m_ImportedPackage; + public void SetLocalPath(IOProxy ioProxy, string path) { m_LocalPath = path ?? string.Empty; @@ -85,7 +89,7 @@ public void SetLocalPath(IOProxy ioProxy, string path) } } - public AssetStorePackageVersion(IOProxy ioProxy, AssetStoreProductInfo productInfo, AssetStoreLocalInfo localInfo = null) + public AssetStorePackageVersion(IOProxy ioProxy, AssetStoreProductInfo productInfo, AssetStoreLocalInfo localInfo = null, AssetStoreImportedPackage importedPackage = null) { if (productInfo == null) throw new ArgumentNullException(nameof(productInfo)); @@ -105,6 +109,8 @@ public AssetStorePackageVersion(IOProxy ioProxy, AssetStoreProductInfo productIn m_VersionId = localInfo?.versionId ?? productInfo.versionId; SemVersionParser.TryParse(m_VersionString.Trim(), out m_Version); + m_ImportedPackage = importedPackage; + var publishDateString = localInfo?.publishedDate ?? productInfo.publishedDate ?? string.Empty; m_PublishedDateTicks = !string.IsNullOrEmpty(publishDateString) ? DateTime.Parse(publishDateString).Ticks : 0; m_DisplayName = !string.IsNullOrEmpty(productInfo.displayName) ? productInfo.displayName : $"Package {productInfo.productId}@{m_VersionId}"; diff --git a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs index ee06e1e10..7db8d277c 100644 --- a/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs +++ b/Modules/PackageManagerUI/Editor/Services/AssetStore/AssetStoreVersionList.cs @@ -46,7 +46,7 @@ public AssetStoreVersionList() m_Versions = new List(); } - public AssetStoreVersionList(IOProxy ioProxy, AssetStoreProductInfo productInfo, AssetStoreLocalInfo localInfo = null, AssetStoreUpdateInfo updateInfo = null) + public AssetStoreVersionList(IOProxy ioProxy, AssetStoreProductInfo productInfo, AssetStoreLocalInfo localInfo = null, AssetStoreUpdateInfo updateInfo = null, AssetStoreImportedPackage importedPackage = null) { m_Versions = new List(); @@ -59,18 +59,21 @@ public AssetStoreVersionList(IOProxy ioProxy, AssetStoreProductInfo productInfo, // result in a case where localInfo and productInfo have different version numbers but no update is available // Because of this, we prefer showing version from the server (even when localInfo version is different) // and we only want to show the localInfo version when `localInfo.canUpdate` is set to true - var latestVersion = new AssetStorePackageVersion(ioProxy, productInfo); - if (localInfo != null) + if (localInfo != null && updateInfo?.canUpdate == true) { - if (updateInfo?.canUpdate == true) - m_Versions.Add(new AssetStorePackageVersion(ioProxy, productInfo, localInfo)); - else + m_Versions.Add(new AssetStorePackageVersion(ioProxy, productInfo, localInfo, importedPackage)); + m_Versions.Add(new AssetStorePackageVersion(ioProxy, productInfo)); + } + else + { + var version = new AssetStorePackageVersion(ioProxy, productInfo, importedPackage: importedPackage); + if (localInfo != null) { - latestVersion.SetLocalPath(ioProxy, localInfo.packagePath); - latestVersion.AddDowngradeWarningIfApplicable(localInfo, updateInfo); + version.SetLocalPath(ioProxy, localInfo.packagePath); + version.AddDowngradeWarningIfApplicable(localInfo, updateInfo); } + m_Versions.Add(version); } - m_Versions.Add(latestVersion); } public IEnumerator GetEnumerator() diff --git a/Modules/PackageManagerUI/Editor/Services/Common/BasePackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Common/BasePackageVersion.cs index 14461177a..3998a4582 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/BasePackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/BasePackageVersion.cs @@ -45,6 +45,8 @@ internal abstract class BasePackageVersion : IPackageVersion, ISerializationCall public virtual DependencyInfo[] resolvedDependencies => null; public virtual EntitlementsInfo entitlements => null; + public virtual IEnumerable importedAssets => null; + public virtual bool isRegistryPackage => false; public virtual bool isFromScopedRegistry => false; diff --git a/Modules/PackageManagerUI/Editor/Services/Common/Package.cs b/Modules/PackageManagerUI/Editor/Services/Common/Package.cs index 11a7d9d8c..1f51158fd 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/Package.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/Package.cs @@ -65,6 +65,9 @@ public PackageState state if (primary != recommended && ((primary.isInstalled && primary != latestKeyVersion) || primary.HasTag(PackageTag.LegacyFormat)) && !primary.HasTag(PackageTag.Local)) return PackageState.UpdateAvailable; + if (primary.importedAssets?.Any() == true) + return PackageState.Imported; + if (versions.importAvailable != null) return PackageState.ImportAvailable; diff --git a/Modules/PackageManagerUI/Editor/Services/Common/ResourceLoader.cs b/Modules/PackageManagerUI/Editor/Services/Common/ResourceLoader.cs index 539b4a98c..4637317fe 100644 --- a/Modules/PackageManagerUI/Editor/Services/Common/ResourceLoader.cs +++ b/Modules/PackageManagerUI/Editor/Services/Common/ResourceLoader.cs @@ -33,7 +33,8 @@ internal static class StyleSheetPath "StyleSheets/Extensions/base/light.uss"; internal static readonly string packageManagerCommon = "StyleSheets/PackageManager/Common.uss"; - internal static readonly string[] packageManagerComponents = { + internal static readonly string[] packageManagerComponents = + { "StyleSheets/PackageManager/PackageDetailsDependenciesTab.uss", "StyleSheets/PackageManager/PackageDetails.uss", "StyleSheets/PackageManager/PackageItem.uss", @@ -42,6 +43,7 @@ internal static class StyleSheetPath "StyleSheets/PackageManager/PackageDetailsSamplesTab.uss", "StyleSheets/PackageManager/PackageDetailsReleasesTab.uss", "StyleSheets/PackageManager/PackageDetailsVersionsTab.uss", + "StyleSheets/PackageManager/PackageDetailsInstalledAssetsTab.uss", "StyleSheets/PackageManager/PackageStatusBar.uss", "StyleSheets/PackageManager/PackageToolbar.uss", "StyleSheets/PackageManager/ProgressBar.uss", @@ -52,6 +54,11 @@ internal static class StyleSheetPath internal static readonly string filtersDropdown = "StyleSheets/PackageManager/Filters.uss"; internal static readonly string inputDropdown = "StyleSheets/PackageManager/InputDropdown.uss"; internal static readonly string inProgressDropdown = "StyleSheets/PackageManager/InProgressDropdown.uss"; + + internal static readonly string selectionWindowCommon = "StyleSheets/PackageManager/SelectionWindow.uss"; + internal static string selectionWindowVariables => EditorGUIUtility.isProSkin ? + "StyleSheets/PackageManager/SelectionWindowDark.uss" : + "StyleSheets/PackageManager/SelectionWindowLight.uss"; } private enum StyleSheetType : int @@ -61,6 +68,7 @@ private enum StyleSheetType : int InputDropdown, FiltersDropdown, InProgressDropdown, + SelectionWindow, Count } @@ -100,7 +108,6 @@ public void OnAfterDeserialize() } } - private StyleSheet FindResolvedStyleSheetFromType(StyleSheetType styleSheetType) { var styleSheetId = resolvedStyleSheetIds[(int)styleSheetType]; @@ -177,6 +184,19 @@ public StyleSheet inProgressDropdownStyleSheet } } + public StyleSheet selectionWindowStyleSheet + { + get + { + var styleSheet = FindResolvedStyleSheetFromType(StyleSheetType.SelectionWindow); + if (styleSheet == null) + styleSheet = ResolveStyleSheets(StyleSheetType.SelectionWindow, + StyleSheetPath.selectionWindowCommon, + StyleSheetPath.selectionWindowVariables); + return styleSheet; + } + } + private StyleSheet ResolveStyleSheets(StyleSheetType styleSheetType, params string[] styleSheetPaths) { return ResolveStyleSheets(styleSheetType, styleSheetPaths.Select(p => diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IOperation.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IOperation.cs index a06b9c8d0..987c78e5a 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IOperation.cs @@ -16,6 +16,7 @@ internal interface IOperation event Action onOperationProgress; string packageUniqueId { get; } + long productId { get; } // a timestamp is added to keep track of how `fresh` the result is // in the case of an online operation, it is the time when the operation starts diff --git a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs index 8cf2ab4de..0582bd460 100644 --- a/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs +++ b/Modules/PackageManagerUI/Editor/Services/Interfaces/IPackageVersion.cs @@ -68,6 +68,8 @@ internal interface IPackageVersion : UI.IPackageVersion DependencyInfo[] resolvedDependencies { get; } + IEnumerable importedAssets { get; } + bool HasTag(PackageTag tag); // A version is fully fetched when the information isn't derived from another version (therefore may be inaccurate) diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageOperationDispatcher.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageOperationDispatcher.cs index f32b7da8e..d8df6b9b0 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageOperationDispatcher.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageOperationDispatcher.cs @@ -12,7 +12,7 @@ namespace UnityEditor.PackageManager.UI.Internal internal class PackageOperationDispatcher { [NonSerialized] - private AssetDatabaseProxy m_AssetDatabase; + private AssetStorePackageInstaller m_AssetStorePackageInstaller; [NonSerialized] private AssetStoreDownloadManager m_AssetStoreDownloadManager; [NonSerialized] @@ -20,12 +20,12 @@ internal class PackageOperationDispatcher [NonSerialized] private IOProxy m_IOProxy; - public void ResolveDependencies(AssetDatabaseProxy assetDatabase, + public void ResolveDependencies(AssetStorePackageInstaller assetStorePackageInstaller, AssetStoreDownloadManager assetStoreDownloadManager, UpmClient upmClient, IOProxy ioProxy) { - m_AssetDatabase = assetDatabase; + m_AssetStorePackageInstaller = assetStorePackageInstaller; m_AssetStoreDownloadManager = assetStoreDownloadManager; m_UpmClient = upmClient; m_IOProxy = ioProxy; @@ -157,12 +157,28 @@ public virtual void Import(IPackage package) try { if (m_IOProxy.FileExists(path)) - m_AssetDatabase.ImportPackage(path, true); + m_AssetStorePackageInstaller.Install(package.product.id, true); } catch (System.IO.IOException e) { Debug.Log($"[Package Manager Window] Cannot import package {package.displayName}: {e.Message}"); } } + + public virtual void RemoveImportedAssets(IPackage package) + { + if (package?.versions.primary?.importedAssets?.Any() != true) + return; + + m_AssetStorePackageInstaller.Uninstall(package.product.id, true); + } + + public virtual void RemoveImportedAssets(IEnumerable versions) + { + if (versions?.Any() != true) + return; + + m_AssetStorePackageInstaller.Uninstall(versions.Select(v => v.package.product.id)); + } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs b/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs index 7ced74b32..1637810a7 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/PackageState.cs @@ -11,6 +11,7 @@ internal enum PackageState InstalledAsDependency, DownloadAvailable, ImportAvailable, + Imported, InDevelopment, UpdateAvailable, InProgress, diff --git a/Modules/PackageManagerUI/Editor/Services/Packages/RefreshOptions.cs b/Modules/PackageManagerUI/Editor/Services/Packages/RefreshOptions.cs index 3d0d8bd21..b53ccac2a 100644 --- a/Modules/PackageManagerUI/Editor/Services/Packages/RefreshOptions.cs +++ b/Modules/PackageManagerUI/Editor/Services/Packages/RefreshOptions.cs @@ -17,7 +17,6 @@ internal enum RefreshOptions : uint UpmSearch = 1 << 3, Purchased = 1 << 4, PurchasedOffline = 1 << 5, - - UpmAny = UpmList | UpmListOffline | UpmSearch | UpmSearchOffline + ImportedAssets = 1 << 6 } } diff --git a/Modules/PackageManagerUI/Editor/Services/Proxies/AssetDatabaseProxy.cs b/Modules/PackageManagerUI/Editor/Services/Proxies/AssetDatabaseProxy.cs index c56445aab..76e7a5e95 100644 --- a/Modules/PackageManagerUI/Editor/Services/Proxies/AssetDatabaseProxy.cs +++ b/Modules/PackageManagerUI/Editor/Services/Proxies/AssetDatabaseProxy.cs @@ -2,30 +2,49 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using UnityEngine; namespace UnityEditor.PackageManager.UI.Internal { [ExcludeFromCodeCoverage] - internal class AssetDatabaseProxy + internal class AssetDatabaseProxy : AssetPostprocessor { + public virtual event Action onPostprocessAllAssets = delegate {}; + + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + ServicesContainer.instance.Resolve().onPostprocessAllAssets?.Invoke(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths); + } + public virtual void ImportPackage(string packagePath, bool interactive) { AssetDatabase.ImportPackage(packagePath, interactive); } + public virtual void ImportPackage(string packagePath, AssetOrigin origin, bool interactive) + { + AssetDatabase.ImportPackage(packagePath, origin, interactive); + } + + public virtual bool DeleteAssets(string[] paths, List outFailedPaths) + { + return AssetDatabase.DeleteAssets(paths, outFailedPaths); + } + public virtual void Refresh() { AssetDatabase.Refresh(); } - public virtual T LoadAssetAtPath(string assetPath) where T : Object + public virtual T LoadAssetAtPath(string assetPath) where T : UnityEngine.Object { return AssetDatabase.LoadAssetAtPath(assetPath); } - public virtual Object LoadMainAssetAtPath(string assetPath) + public virtual UnityEngine.Object LoadMainAssetAtPath(string assetPath) { return AssetDatabase.LoadMainAssetAtPath(assetPath); } @@ -34,5 +53,25 @@ public virtual bool GetAssetFolderInfo(string path, out bool rootFolder, out boo { return AssetDatabase.GetAssetFolderInfo(path, out rootFolder, out immutable); } + + public virtual AssetOrigin GetAssetOrigin(string guid) + { + return AssetDatabase.GetAssetOrigin(guid); + } + + public virtual string GUIDToAssetPath(string guid) + { + return AssetDatabase.GUIDToAssetPath(guid); + } + + public virtual string AssetPathToGUID(string path) + { + return AssetDatabase.AssetPathToGUID(path); + } + + public virtual string[] FindAssets(SearchFilter filter) + { + return AssetDatabase.FindAssets(filter); + } } } diff --git a/Modules/PackageManagerUI/Editor/Services/ServicesContainer.cs b/Modules/PackageManagerUI/Editor/Services/ServicesContainer.cs index c5e016a88..e1d375144 100644 --- a/Modules/PackageManagerUI/Editor/Services/ServicesContainer.cs +++ b/Modules/PackageManagerUI/Editor/Services/ServicesContainer.cs @@ -65,6 +65,12 @@ internal sealed class ServicesContainer : ScriptableSingleton private AssetStoreRestAPI m_AssetStoreRestAPI; + private AssetStorePackageInstaller m_AssetStorePackageInstaller; + + private AssetSelectionHandler m_AssetSelectionHandler; + + private SelectionWindowProxy m_SelectionWindowProxy; + [SerializeField] private AssetStoreDownloadManager m_AssetStoreDownloadManager; @@ -139,6 +145,7 @@ public void Reload() m_IOProxy = new IOProxy(); m_SettingsProxy = new PackageManagerProjectSettingsProxy(); m_ClientProxy = new ClientProxy(); + m_SelectionWindowProxy = new SelectionWindowProxy(); if (m_ResourceLoader != null) m_ResourceLoader.Reset(); @@ -154,9 +161,11 @@ public void Reload() m_AssetStoreUtils = new AssetStoreUtils(); m_JsonParser = new JsonParser(); m_AssetStoreRestAPI = new AssetStoreRestAPI(); + m_AssetStorePackageInstaller = new AssetStorePackageInstaller(); m_AssetStoreDownloadManager = new AssetStoreDownloadManager(); m_AssetStoreCallQueue = new AssetStoreCallQueue(); m_AssetStoreCachePathProxy = new AssetStoreCachePathProxy(); + m_AssetSelectionHandler = new AssetSelectionHandler(); m_UpmCache = new UpmCache(); m_UpmClient = new UpmClient(); @@ -191,11 +200,13 @@ private void ResolveDependencies() m_ResourceLoader.ResolveDependencies(m_ApplicationProxy); m_AssetStoreCache.ResolveDependencies(m_ApplicationProxy, m_HttpClientFactory, m_IOProxy, m_UniqueIdMapper); - m_AssetStoreClient.ResolveDependencies(m_UnityConnectProxy, m_AssetStoreCache, m_AssetStoreUtils, m_AssetStoreRestAPI, m_FetchStatusTracker, m_UpmCache); + m_AssetStoreClient.ResolveDependencies(m_UnityConnectProxy, m_AssetStoreCache, m_AssetStoreUtils, m_AssetStoreRestAPI, m_FetchStatusTracker, m_AssetDatabaseProxy); m_AssetStoreOAuth.ResolveDependencies(m_UnityConnectProxy, m_UnityOAuthProxy, m_HttpClientFactory); m_AssetStoreRestAPI.ResolveDependencies(m_UnityConnectProxy, m_AssetStoreOAuth, m_JsonParser, m_HttpClientFactory); + m_AssetStorePackageInstaller.ResolveDependencies(m_IOProxy, m_AssetStoreCache, m_AssetDatabaseProxy, m_AssetSelectionHandler); m_AssetStoreDownloadManager.ResolveDependencies(m_ApplicationProxy, m_HttpClientFactory, m_UnityConnectProxy, m_IOProxy, m_AssetStoreCache, m_AssetStoreUtils, m_AssetStoreRestAPI, m_AssetStoreCachePathProxy); m_AssetStoreCallQueue.ResolveDependencies(m_ApplicationProxy, m_UnityConnectProxy, m_PackageManagerPrefs, m_AssetStoreClient, m_AssetStoreCache, m_PageManager, m_PageRefreshHandler); + m_AssetSelectionHandler.ResolveDependencies(m_SelectionWindowProxy); m_UpmCache.ResolveDependencies(m_UniqueIdMapper); m_UpmClient.ResolveDependencies(m_UpmCache, m_FetchStatusTracker, m_IOProxy, m_SettingsProxy, m_ClientProxy, m_ApplicationProxy); @@ -203,10 +214,10 @@ private void ResolveDependencies() m_UpmCacheRootClient.ResolveDependencies(m_ClientProxy, m_ApplicationProxy); m_PackageDatabase.ResolveDependencies(m_UniqueIdMapper, m_AssetDatabaseProxy, m_UpmCache, m_IOProxy); - m_OperationDispatcher.ResolveDependencies(m_AssetDatabaseProxy, m_AssetStoreDownloadManager, m_UpmClient, m_IOProxy); + m_OperationDispatcher.ResolveDependencies(m_AssetStorePackageInstaller, m_AssetStoreDownloadManager, m_UpmClient, m_IOProxy); m_InspectorSelectionHandler.ResolveDependencies(m_SelectionProxy, m_PackageManagerPrefs, m_PackageDatabase, m_PageManager); m_PageManager.ResolveDependencies(m_UnityConnectProxy, m_PackageManagerPrefs, m_AssetStoreClient, m_PackageDatabase, m_SettingsProxy); - m_PageRefreshHandler.ResolveDependencies(m_PageManager, m_ApplicationProxy, m_UnityConnectProxy, m_PackageManagerPrefs, m_UpmClient, m_UpmRegistryClient, m_AssetStoreClient); + m_PageRefreshHandler.ResolveDependencies(m_PageManager, m_ApplicationProxy, m_UnityConnectProxy, m_AssetDatabaseProxy, m_PackageManagerPrefs, m_UpmClient, m_UpmRegistryClient, m_AssetStoreClient); m_AssetStorePackageFactory.ResolveDependencies(m_UniqueIdMapper, m_UnityConnectProxy, m_AssetStoreCache, m_AssetStoreClient, m_AssetStoreDownloadManager, m_PackageDatabase, m_FetchStatusTracker, m_IOProxy); m_UpmPackageFactory.ResolveDependencies(m_UniqueIdMapper, m_UpmCache, m_UpmClient, m_PackageDatabase, m_SettingsProxy); @@ -235,6 +246,7 @@ private void Initialize() m_UnityConnectProxy.OnEnable(); m_ApplicationProxy.OnEnable(); m_SettingsProxy.OnEnable(); + m_SelectionWindowProxy.OnEnable(); m_AssetStoreOAuth.OnEnable(); m_AssetStoreDownloadManager.OnEnable(); @@ -250,6 +262,9 @@ private void Initialize() m_UpmPackageFactory.OnEnable(); m_UpmOnAssetStorePackageFactory.OnEnable(); + m_AssetStorePackageInstaller.OnEnable(); + m_AssetSelectionHandler.OnEnable(); + m_InitializeState = State.Initialized; } @@ -258,6 +273,7 @@ public void OnDisable() m_UnityConnectProxy.OnDisable(); m_ApplicationProxy.OnDisable(); m_SettingsProxy.OnDisable(); + m_SelectionWindowProxy.OnDisable(); m_AssetStoreOAuth.OnDisable(); m_AssetStoreDownloadManager.OnDisable(); @@ -272,6 +288,9 @@ public void OnDisable() m_AssetStorePackageFactory.OnDisable(); m_UpmPackageFactory.OnDisable(); m_UpmOnAssetStorePackageFactory.OnDisable(); + + m_AssetStorePackageInstaller.OnDisable(); + m_AssetSelectionHandler.OnDisable(); } public void RegisterDefaultServices() @@ -288,6 +307,7 @@ public void RegisterDefaultServices() Register(m_IOProxy); Register(m_SettingsProxy); Register(m_ClientProxy); + Register(m_SelectionWindowProxy); Register(m_FetchStatusTracker); Register(m_UniqueIdMapper); @@ -299,6 +319,7 @@ public void RegisterDefaultServices() Register(m_AssetStoreRestAPI); Register(m_AssetStoreCallQueue); Register(m_AssetStoreCachePathProxy); + Register(m_AssetSelectionHandler); Register(m_UpmCache); Register(m_UpmClient); @@ -317,6 +338,7 @@ public void RegisterDefaultServices() Register(m_UpmPackageFactory); Register(m_UpmOnAssetStorePackageFactory); + Register(m_AssetStorePackageInstaller); Register(m_AssetStoreDownloadManager); } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs index 189522ac5..9e3871dc0 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmBaseOperation.cs @@ -30,6 +30,7 @@ public virtual string packageName protected string m_PackageIdOrName = string.Empty; public virtual string packageIdOrName => m_PackageIdOrName; + public virtual long productId => 0; public string packageUniqueId => packageName; [SerializeField] diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs index 8a3a7dfc8..04e963904 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmClient.cs @@ -46,6 +46,9 @@ internal class UpmClient : ISerializationCallbackReceiver private UpmRemoveOperation m_RemoveOperation; private UpmRemoveOperation removeOperation => CreateOperation(ref m_RemoveOperation); + [SerializeField] + private UpmSearchOperation[] m_SerializedInProgressExtraFetchOperations = Array.Empty(); + private readonly Dictionary m_ExtraFetchOperations = new Dictionary(); [SerializeField] @@ -97,6 +100,8 @@ public void OnBeforeSerialize() { m_SerializedRegistryUrlsKeys = m_RegistryUrls?.Keys.ToArray() ?? new string[0]; m_SerializedRegistryUrlsValues = m_RegistryUrls?.Values.ToArray() ?? new bool[0]; + + m_SerializedInProgressExtraFetchOperations = m_ExtraFetchOperations?.Values.Where(i => i.isInProgress).ToArray() ?? new UpmSearchOperation[0]; } public void OnAfterDeserialize() @@ -409,7 +414,7 @@ private UpmSearchOperation ExtraFetchInternal(string packageIdOrName, long produ return null; var operation = new UpmSearchOperation(); operation.ResolveDependencies(m_ClientProxy, m_ApplicationProxy); - operation.Search(packageIdOrName); + operation.Search(packageIdOrName, productId); operation.onProcessResult += (requst) => OnProcessExtraFetchResult(requst, productId); operation.onOperationFinalized += (op) => m_ExtraFetchOperations.Remove(packageIdOrName); m_ExtraFetchOperations[packageIdOrName] = operation; @@ -479,6 +484,9 @@ private void RestoreInProgressOperations() if (m_SearchOperation?.isInProgress ?? false) SearchAll(); + + foreach (var operation in m_SerializedInProgressExtraFetchOperations) + ExtraFetchInternal(operation.packageIdOrName, operation.productId); } public void OnEnable() diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs index fa036195e..ababb146c 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmSearchOperation.cs @@ -4,6 +4,7 @@ using System; using UnityEditor.PackageManager.Requests; +using UnityEngine; namespace UnityEditor.PackageManager.UI.Internal { @@ -14,10 +15,15 @@ internal class UpmSearchOperation : UpmBaseOperation protected override string operationErrorMessage => isOfflineMode ? L10n.Tr("Error searching for packages offline.") : L10n.Tr("Error searching for packages."); + [SerializeField] + private long m_ProductId; + public override long productId => m_ProductId; + public void SearchAll() { m_OfflineMode = false; m_PackageIdOrName = string.Empty; + m_ProductId = 0; Start(); } @@ -26,13 +32,15 @@ public void SearchAllOffline(long timestamp) m_OfflineMode = true; m_Timestamp = timestamp; m_PackageIdOrName = string.Empty; + m_ProductId = 0; Start(); } - public void Search(string packageIdOrName) + public void Search(string packageIdOrName, long productId) { m_OfflineMode = false; m_PackageIdOrName = packageIdOrName; + m_ProductId = productId; Start(); } @@ -41,6 +49,7 @@ public void SearchOffline(string packageIdOrName, long timestamp) m_OfflineMode = true; m_Timestamp = timestamp; m_PackageIdOrName = packageIdOrName; + m_ProductId = 0; Start(); } diff --git a/Modules/PackageManagerUI/Editor/UI/Common/PageFilters.cs b/Modules/PackageManagerUI/Editor/UI/Common/PageFilters.cs index 2062b2e62..b282db070 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/PageFilters.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/PageFilters.cs @@ -13,6 +13,7 @@ internal class PageFilters : IEquatable { internal const string k_UnlabeledStatus = "Unlabeled"; internal const string k_DownloadedStatus = "Downloaded"; + internal const string k_ImportedStatus = "Imported"; internal const string k_UpdateAvailableStatus = "Update available"; internal const string k_SubscriptionBasedStatus = "Subscription based"; @@ -29,6 +30,7 @@ public string status } public bool downloadedOnly => k_DownloadedStatus.Equals(status, StringComparison.OrdinalIgnoreCase); + public bool importedOnly => k_ImportedStatus.Equals(status, StringComparison.OrdinalIgnoreCase); public bool updateAvailableOnly => k_UpdateAvailableStatus.Equals(status, StringComparison.OrdinalIgnoreCase); public bool subscriptionBasedOnly => k_SubscriptionBasedStatus.Equals(status, StringComparison.OrdinalIgnoreCase); diff --git a/Modules/PackageManagerUI/Editor/UI/Common/PageRefreshHandler.cs b/Modules/PackageManagerUI/Editor/UI/Common/PageRefreshHandler.cs index 113080530..c6d115f54 100644 --- a/Modules/PackageManagerUI/Editor/UI/Common/PageRefreshHandler.cs +++ b/Modules/PackageManagerUI/Editor/UI/Common/PageRefreshHandler.cs @@ -10,14 +10,14 @@ namespace UnityEditor.PackageManager.UI.Internal { [Serializable] - internal class PageRefreshHandler : ISerializationCallbackReceiver + internal class PageRefreshHandler: ISerializationCallbackReceiver { private static readonly RefreshOptions[] k_RefreshOptionsByTab = { RefreshOptions.UpmList | RefreshOptions.UpmSearch, // PackageFilterTab.UnityRegistry RefreshOptions.UpmList, // PackageFilterTab.InProject RefreshOptions.UpmListOffline | RefreshOptions.UpmSearchOffline, // PackageFilterTab.BuiltIn - RefreshOptions.Purchased, // PackageFilterTab.AssetStore + RefreshOptions.Purchased | RefreshOptions.ImportedAssets, // PackageFilterTab.AssetStore RefreshOptions.UpmList | RefreshOptions.UpmSearch, // PackageFilterTab.MyRegistries }; @@ -55,6 +55,8 @@ internal class PageRefreshHandler : ISerializationCallbackReceiver [NonSerialized] private UnityConnectProxy m_UnityConnect; [NonSerialized] + private AssetDatabaseProxy m_AssetDatabase; + [NonSerialized] private PackageManagerPrefs m_PackageManagerPrefs; [NonSerialized] private AssetStoreClientV2 m_AssetStoreClient; @@ -62,6 +64,7 @@ internal class PageRefreshHandler : ISerializationCallbackReceiver public void ResolveDependencies(PageManager pageManager, ApplicationProxy application, UnityConnectProxy unityConnect, + AssetDatabaseProxy assetDatabase, PackageManagerPrefs packageManagerPrefs, UpmClient upmClient, UpmRegistryClient upmRegistryClient, @@ -72,6 +75,7 @@ public void ResolveDependencies(PageManager pageManager, m_UpmClient = upmClient; m_UpmRegistryClient = upmRegistryClient; m_UnityConnect = unityConnect; + m_AssetDatabase = assetDatabase; m_PackageManagerPrefs = packageManagerPrefs; m_AssetStoreClient = assetStoreClient; } @@ -95,7 +99,7 @@ public void OnAfterDeserialize() private void OnFilterChanged(PackageFilterTab filterTab) { - if (GetRefreshTimestamp(filterTab) == 0) + if (!IsInitialFetchingDone(filterTab)) Refresh(filterTab); } @@ -117,7 +121,7 @@ public virtual void Refresh(RefreshOptions options) m_UpmClient.SearchAll(); // Since the SearchAll online call now might return error and an empty list, we want to trigger a `SearchOffline` call if // we detect that SearchOffline has not been called before. That way we will have some offline result to show to the user instead of nothing - if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmSearchOffline, out var value) || value == 0) + if (GetRefreshTimestampSingleFlag(RefreshOptions.UpmSearchOffline) == 0) options |= RefreshOptions.UpmSearchOffline; } if ((options & RefreshOptions.UpmSearchOffline) != 0) @@ -126,7 +130,7 @@ public virtual void Refresh(RefreshOptions options) { m_UpmClient.List(); // Do the same logic for the List operations as the Search operations - if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmListOffline, out var value) || value == 0) + if (GetRefreshTimestampSingleFlag(RefreshOptions.UpmListOffline) == 0) options |= RefreshOptions.UpmListOffline; } if ((options & RefreshOptions.UpmListOffline) != 0) @@ -140,6 +144,27 @@ public virtual void Refresh(RefreshOptions options) } if ((options & RefreshOptions.PurchasedOffline) != 0) m_AssetStoreClient.RefreshLocal(); + if ((options & RefreshOptions.ImportedAssets) != 0) + RefreshImportedAssets(); + } + + private void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + RefreshImportedAssets(true); + } + + private void RefreshImportedAssets(bool forceRefresh = false) + { + // When `OnPostprocessAllAssets` call `RefreshImportedAssets` because asset changes are detected, we set the + // forceRefresh to true so we don't missed any potential modified asset + // When `RefreshImportedAssets` is triggered by a user page refresh, we only want to actually call the refresh + // if it was never called before, because we don't want to scan through Asset Database all the time and `OnPostprocessAllAssets` + // will catch all the changes anyways. + if (forceRefresh || GetRefreshTimestampSingleFlag(RefreshOptions.ImportedAssets) == 0) + { + m_AssetStoreClient.RefreshImportedAssets(); + m_RefreshTimestamps[RefreshOptions.ImportedAssets] = DateTime.Now.Ticks; + } } public virtual void CancelRefresh(PackageFilterTab? tab = null) @@ -157,9 +182,10 @@ private void OnUserLoginStateChange(bool userInfoReady, bool loggedIn) { if (!loggedIn) { - // We also want to clear the refresh time stamp here so that the next time users visit the Asset Store page, we'll call - // refresh properly + // We also want to clear the refresh time stamp here so that the next time users visit the Asset Store page, + // we'll call refresh properly m_RefreshTimestamps[RefreshOptions.Purchased] = 0; + m_RefreshErrors.Remove(RefreshOptions.Purchased); } else if (m_PackageManagerPrefs.currentFilterTab == PackageFilterTab.AssetStore && m_Application.isInternetReachable && @@ -172,6 +198,8 @@ public void OnEnable() { InitializeRefreshTimestamps(); + m_AssetDatabase.onPostprocessAllAssets += OnPostprocessAllAssets; + m_UpmClient.onListOperation += OnRefreshOperation; m_UpmClient.onSearchAllOperation += OnRefreshOperation; m_AssetStoreClient.onListOperation += OnRefreshOperation; @@ -183,6 +211,8 @@ public void OnEnable() public void OnDisable() { + m_AssetDatabase.onPostprocessAllAssets -= OnPostprocessAllAssets; + m_UpmClient.onListOperation -= OnRefreshOperation; m_UpmClient.onSearchAllOperation -= OnRefreshOperation; m_AssetStoreClient.onListOperation -= OnRefreshOperation; @@ -197,10 +227,7 @@ public void InitializeRefreshTimestamps() { foreach (RefreshOptions filter in Enum.GetValues(typeof(RefreshOptions))) { - if (filter == RefreshOptions.None || filter == RefreshOptions.UpmAny) - continue; - - if (m_RefreshTimestamps.ContainsKey(filter)) + if (filter == RefreshOptions.None || m_RefreshTimestamps.ContainsKey(filter)) continue; m_RefreshTimestamps[filter] = 0; } @@ -229,13 +256,13 @@ private void OnRefreshOperationSuccess(IOperation operation) { // when an online operation successfully returns with a timestamp newer than the offline timestamp, we update the offline timestamp as well // since we merge the online & offline result in the PackageDatabase and it's the newer ones that are being shown - if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmSearchOffline, out var value) || value < operation.timestamp) + if (GetRefreshTimestampSingleFlag(RefreshOptions.UpmSearchOffline) < operation.timestamp) m_RefreshTimestamps[RefreshOptions.UpmSearchOffline] = operation.timestamp; } else if (operation.refreshOptions == RefreshOptions.UpmList) { // Do the same logic for the List operations as the Search operations - if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmListOffline, out var value) || value < operation.timestamp) + if (GetRefreshTimestampSingleFlag(RefreshOptions.UpmListOffline) < operation.timestamp) m_RefreshTimestamps[RefreshOptions.UpmListOffline] = operation.timestamp; } if (m_RefreshErrors.ContainsKey(operation.refreshOptions)) @@ -288,6 +315,14 @@ public virtual long GetRefreshTimestamp(RefreshOptions option) return result; } + // This function only work with single flag (e.g. `RefreshOption.UpmList`) refresh option. + // If a refresh option with multiple flags (e.g. `RefreshOption.UpmList | RefreshOption.UpmSearch`) + // is passed, the result won't be correct. + private long GetRefreshTimestampSingleFlag(RefreshOptions option) + { + return m_RefreshTimestamps.TryGetValue(option, out var value) ? value : 0; + } + public virtual UIError GetRefreshError(RefreshOptions option) { // only return the first one when there are multiple errors diff --git a/Modules/PackageManagerUI/Editor/UI/Filters/AssetStoreFiltersWindow.cs b/Modules/PackageManagerUI/Editor/UI/Filters/AssetStoreFiltersWindow.cs index b8a36d2d5..760095901 100644 --- a/Modules/PackageManagerUI/Editor/UI/Filters/AssetStoreFiltersWindow.cs +++ b/Modules/PackageManagerUI/Editor/UI/Filters/AssetStoreFiltersWindow.cs @@ -15,6 +15,7 @@ internal class AssetStoreFiltersWindow : PackageManagerFiltersWindow internal static readonly string[] k_Statuses = { PageFilters.k_DownloadedStatus, + PageFilters.k_ImportedStatus, PageFilters.k_UpdateAvailableStatus, PageFilters.k_UnlabeledStatus, "Hidden", diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs index 01cfd2c3a..24b7af43c 100644 --- a/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/MultiSelectDetails.cs @@ -49,6 +49,7 @@ private void ResolveDependencies() private DownloadFoldoutGroup m_DownloadFoldoutGroup; private DownloadUpdateFoldoutGroup m_DownloadUpdateFoldoutGroup; + private RemoveImportedFoldoutGroup m_RemoveImportedFoldoutGroup; private MultiSelectFoldoutGroup[] m_UpmFoldoutGroups; private MultiSelectFoldoutGroup[] m_AssetStoreFoldoutGroups; @@ -116,10 +117,13 @@ private void InitializeFoldouts() m_DownloadUpdateFoldoutGroup.mainButton.SetGlobalDisableConditions(disableIfCompiling, disableIfNoNetwork); m_DownloadUpdateFoldoutGroup.cancelButton.SetGlobalDisableConditions(disableIfCompiling); + m_RemoveImportedFoldoutGroup = new RemoveImportedFoldoutGroup(m_Application, m_OperationDispatcher); + m_RemoveImportedFoldoutGroup.mainButton.SetGlobalDisableConditions(disableIfCompiling); + m_UpmFoldoutGroups = new MultiSelectFoldoutGroup[] { m_InstallFoldoutGroup, m_RemoveFoldoutGroup, m_UpdateFoldoutGroup }; // Now that Upm packages can live on Asset Store, we want to show Upm foldout groups on asset store too - m_AssetStoreFoldoutGroups = new MultiSelectFoldoutGroup[] { m_DownloadFoldoutGroup, m_DownloadUpdateFoldoutGroup, m_InstallFoldoutGroup, m_RemoveFoldoutGroup, m_UpdateFoldoutGroup }; + m_AssetStoreFoldoutGroups = new MultiSelectFoldoutGroup[] { m_DownloadFoldoutGroup, m_DownloadUpdateFoldoutGroup, m_RemoveImportedFoldoutGroup, m_InstallFoldoutGroup, m_RemoveFoldoutGroup, m_UpdateFoldoutGroup }; // Add foldouts to the UI in the correct order. Note that the order here is not the same as the initialization order from above. foldoutsContainer.Add(m_UnlockFoldout); @@ -136,6 +140,9 @@ private void InitializeFoldouts() foldoutsContainer.Add(m_DownloadUpdateFoldoutGroup.mainFoldout); foldoutsContainer.Add(m_DownloadUpdateFoldoutGroup.inProgressFoldout); + foldoutsContainer.Add(m_RemoveImportedFoldoutGroup.mainFoldout); + foldoutsContainer.Add(m_RemoveImportedFoldoutGroup.inProgressFoldout); + foldoutsContainer.Add(m_CheckUpdateFoldout); foldoutsContainer.Add(m_NoActionFoldout); } diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveFoldoutGroup.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveFoldoutGroup.cs index ee472ab02..d13925bc7 100644 --- a/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveFoldoutGroup.cs +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveFoldoutGroup.cs @@ -34,9 +34,7 @@ public override void Refresh() public override bool AddPackageVersion(IPackageVersion version) { - if (!version.HasTag(PackageTag.UpmFormat)) - return false; - return base.AddPackageVersion(version); + return version.HasTag(PackageTag.UpmFormat) && base.AddPackageVersion(version); } } } diff --git a/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveImportedFoldoutGroup.cs b/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveImportedFoldoutGroup.cs new file mode 100644 index 000000000..c714745d8 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/UI/MultiSelect/RemoveImportedFoldoutGroup.cs @@ -0,0 +1,28 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Linq; + +namespace UnityEditor.PackageManager.UI.Internal +{ + internal class RemoveImportedFoldoutGroup : MultiSelectFoldoutGroup + { + public RemoveImportedFoldoutGroup(ApplicationProxy applicationProxy, PackageOperationDispatcher operationDispatcher) + : base(new PackageRemoveImportedButton(applicationProxy,operationDispatcher), null) + { + } + + public override void Refresh() + { + mainFoldout.headerTextTemplate = L10n.Tr("Remove imported assets from {0}"); + inProgressFoldout.headerTextTemplate = L10n.Tr("Removing imported assets from {0}"); + base.Refresh(); + } + + public override bool AddPackageVersion(IPackageVersion version) + { + return version.importedAssets?.Any() == true && base.AddPackageVersion(version); + } + } +} diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs index 2cb42553b..a00235a10 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetails.cs @@ -103,6 +103,10 @@ private void RecalculateFillerHeight(GeometryChangedEvent evt) var featureDependencies = tabView.GetTab(FeatureDependenciesTab.k_Id); featureDependencies?.RecalculateFillerHeight(detail.layout.height, scrollView.layout.height); + var importedAssets = tabView.GetTab(PackageDetailsImportedAssetsTab.k_Id); + importedAssets?.RecalculateTabHeight(detail.layout.height, scrollView.layout.height, header.layout.height, + tabViewHeaderContainer.layout.height, customContainer.layout.height, + extensionContainer.layout.height); } private void OnDetailScroll(float offset) @@ -229,5 +233,6 @@ private void RefreshDetailError(IPackage package, IPackageVersion version) internal VisualElement extensionContainer => cache.Get("detailExtensionContainer"); private PackageDetailsTabView tabView => cache.Get("packageDetailsTabView"); + private VisualElement tabViewHeaderContainer => cache.Get("packageDetailsTabViewHeaderContainer"); } } diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs index 6b27cea43..a0392612c 100644 --- a/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsBody.cs @@ -66,6 +66,7 @@ public void AddTabs() m_TabView.AddTab(new PackageDetailsDescriptionTab(m_ResourceLoader, m_PackageManagerPrefs)); m_TabView.AddTab(new PackageDetailsOverviewTab(m_ResourceLoader)); m_TabView.AddTab(new PackageDetailsReleasesTab()); + m_TabView.AddTab(new PackageDetailsImportedAssetsTab(m_IOProxy, m_PackageManagerPrefs)); m_TabView.AddTab(new PackageDetailsVersionsTab(m_ResourceLoader, m_Application, m_PackageManagerPrefs, m_PackageDatabase, m_OperationDispatcher, m_PageManager, m_SettingsProxy, m_UpmCache, m_IOProxy)); m_TabView.AddTab(new PackageDetailsDependenciesTab(m_ResourceLoader, m_PackageDatabase)); m_TabView.AddTab(new FeatureDependenciesTab(m_ResourceLoader, m_PackageDatabase, m_PackageManagerPrefs, m_SettingsProxy, m_Application)); diff --git a/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsImportedAssetsTab.cs b/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsImportedAssetsTab.cs new file mode 100644 index 000000000..5eb501105 --- /dev/null +++ b/Modules/PackageManagerUI/Editor/UI/PackageDetailsTabs/PackageDetailsImportedAssetsTab.cs @@ -0,0 +1,194 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditorInternal; +using UnityEngine.UIElements; + +namespace UnityEditor.PackageManager.UI.Internal +{ + internal class PackageDetailsImportedAssetsTab : PackageDetailsTabElement + { + internal const string k_Id = "importedassets"; + internal const int k_LabelColumnId = 0; + internal const int k_LocationColumnId = 1; + internal const int k_VersionColumnId = 2; + private const int k_MinColumnWidth = 70; + + private static readonly string k_LabelColumnTitle = L10n.Tr("Asset name"); + private static readonly string k_LocationColumnTitle = L10n.Tr("Location in project"); + private static readonly string k_VersionColumnTitle = L10n.Tr("Version"); + + private MultiColumnListView m_ListView; + private IOProxy m_IOProxy; + private PackageManagerPrefs m_PackageManagerPrefs; + + private IList assets => m_ListView.itemsSource as IList; + + public override bool IsValid(IPackageVersion version) + { + return version?.importedAssets?.Any() == true; + } + + public PackageDetailsImportedAssetsTab(IOProxy iOProxy, PackageManagerPrefs packageManagerPrefs) + { + m_IOProxy = iOProxy; + m_PackageManagerPrefs = packageManagerPrefs; + + m_Id = k_Id; + m_DisplayName = L10n.Tr("Imported Assets"); + + name = "importedAssetsDetailsContainer"; + m_ListView = new MultiColumnListView + { + sortingEnabled = true, + name = "InstalledAssetsList", + scrollView = { verticalScrollerVisibility = ScrollerVisibility.Auto}, + selectionType = SelectionType.None, + itemsSource = Array.Empty(), + columns = { reorderable = false } + }; + Add(m_ListView); + AddColumnsAndRestoreSorting(); + + m_ListView.columnSortingChanged += OnColumnSortingChanged; + } + + public override void Refresh(IPackageVersion version) + { + SortAssetsAndRefreshItems(version.importedAssets); + } + + private void AddColumnsAndRestoreSorting() + { + var columns = new Column[3]; + columns[k_LabelColumnId] = new Column + { + name = "label", + makeHeader = () => new Label + { + name = "columnHeader", + text = k_LabelColumnTitle, + tooltip = k_LabelColumnTitle + }, + makeCell = () => + { + var ve = new VisualElement { name = "iconAndLabelContainer" }; + ve.Add(new Image()); + ve.Add(new Label()); + return ve; + }, + bindCell = (ve, index) => + { + var image = ve.Q(); + image.image = InternalEditorUtility.GetIconForFile(assets[index].importedPath); + var label = ve.Q