Example #1
0
        private async void RootUIElementsChanged(object sender, [NotNull] ItemChangeEventArgs e)
        {
            UIElement rootElement;

            switch (e.ChangeType)
            {
            case ContentChangeType.CollectionUpdate:
                break;

            case ContentChangeType.CollectionAdd:
                rootElement = (UIElement)e.NewValue;
                Editor.Logger.Verbose($"Add {rootElement.Id} to the RootElements collection");
                break;

            case ContentChangeType.CollectionRemove:
                rootElement = (UIElement)e.OldValue;
                Editor.Logger.Verbose($"Remove {rootElement.Id} from the RootElements collection");
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            // Update view model and replicate changes to the game-side objects
            if (e.ChangeType == ContentChangeType.CollectionAdd)
            {
                var element     = (UIElement)e.NewValue;
                var childDesign = UIAsset.Hierarchy.Parts[element.Id];
                var viewModel   = (UIElementViewModel)Editor.CreatePartViewModel(Asset, childDesign);
                InsertItem(e.Index.Int, viewModel);

                // Collect children that need to be notified
                var childrenToNotify = viewModel.Children.BreadthFirst(x => x.Children).ToList();
                // Add the element to the game, then notify
                await Editor.Controller.AddPart(this, viewModel.AssetSideUIElement);

                // Manually notify the game-side scene
                viewModel.NotifyGameSidePartAdded().Forget();
                foreach (var child in childrenToNotify)
                {
                    child.NotifyGameSidePartAdded().Forget();
                }
            }
            else if (e.ChangeType == ContentChangeType.CollectionRemove)
            {
                var partId  = new AbsoluteId(Asset.Id, ((UIElement)e.OldValue).Id);
                var element = (UIElementViewModel)Editor.FindPartViewModel(partId);
                if (element == null)
                {
                    throw new InvalidOperationException($"{nameof(element)} can't be null");
                }
                RemoveChildViewModel(element);
                Editor.Controller.RemovePart(this, element.AssetSideUIElement).Forget();
            }

            OnRootUIElementsChanged(e);
        }
Example #2
0
        public NavigationMeshManager([NotNull] IEditorGameController controller)
        {
            referencerId = new AbsoluteId(AssetId.Empty, Guid.NewGuid());
            loader       = controller.Loader;
            var root = controller.GameSideNodeContainer.GetOrCreateNode(this);

            meshesNode              = root[nameof(Meshes)].Target;
            meshesNode.ItemChanged += (sender, args) => { Changed?.Invoke(this, args); };
        }
Example #3
0
        private async void ChildrenContentChanged(object sender, ItemChangeEventArgs e)
        {
            UIElement childElement;

            switch (e.ChangeType)
            {
            case ContentChangeType.CollectionUpdate:
                return;

            case ContentChangeType.CollectionAdd:
                childElement = (UIElement)e.NewValue;
                Editor.Logger.Debug($"Add {childElement.Name ?? childElement.GetType().Name} ({childElement.Id}) to the {nameof(Children)} collection");
                break;

            case ContentChangeType.CollectionRemove:
                childElement = (UIElement)e.OldValue;
                Editor.Logger.Debug($"Remove {childElement.Name ?? childElement.GetType().Name} ({childElement.Id}) from the {nameof(Children)} collection");
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            // Update view model
            if (e.ChangeType == ContentChangeType.CollectionAdd)
            {
                var elementDesign = UIAsset.Hierarchy.Parts[childElement.Id];
                var viewModel     = (UIElementViewModel)Editor.CreatePartViewModel(Asset, elementDesign);
                InsertItem(e.Index.Int, viewModel);

                // Collect children that need to be notified
                var childrenToNotify = viewModel.Children.BreadthFirst(x => x.Children).ToList();
                // Add the element to the game, then notify
                await Editor.Controller.AddPart(this, viewModel.AssetSideUIElement);

                // Manually notify the game-side scene
                viewModel.NotifyGameSidePartAdded().Forget();
                foreach (var child in childrenToNotify)
                {
                    child.NotifyGameSidePartAdded().Forget();
                }
            }
            else if (e.ChangeType == ContentChangeType.CollectionRemove)
            {
                var partId  = new AbsoluteId(Asset.Id, childElement.Id);
                var element = (UIElementViewModel)Editor.FindPartViewModel(partId);
                if (element == null)
                {
                    throw new InvalidOperationException($"{nameof(element)} cannot be null");
                }
                RemoveChildViewModel(element);
                Editor.Controller.RemovePart(this, element.AssetSideUIElement).Forget();
            }
        }
Example #4
0
        private bool TryGetElementIdAtPosition(ref Vector3 worldPosition, out Guid elementId)
        {
            var   hitResults   = GetAdornerVisualsAtPosition(ref worldPosition) as List <UIRenderFeature.HitTestResult>;
            float smallestArea = float.MaxValue;

            elementId = default;
            var uiComponent = Controller.GetEntityByName(UIEditorController.AdornerEntityName).Get <UIComponent>();

            for (int i = 0; i < hitResults.Count; i++)
            {
                UIRenderFeature.HitTestResult hr = hitResults[i];
                if (hr.Element.DependencyProperties.TryGetValue(AssociatedElementIdPropertyKey, out var thisElementId))
                {
                    var editor    = Controller.Editor;
                    var partId    = new AbsoluteId(editor.Asset.Id, thisElementId);
                    var element   = editor.FindPartViewModel(partId);
                    var uielement = (element as UIElementViewModel)?.AssetSideUIElement as UIElement;

                    if (uielement?.IsVisibleInTree ?? false)
                    {
                        // make sure we get a size that isn't NaN
                        float     mysize;
                        UIElement lookat = uielement;
                        do
                        {
                            mysize = lookat.Width * lookat.Height;
                            if (float.IsNaN(mysize) == false)
                            {
                                break;
                            }
                            if (uielement is TextBlock tb)
                            {
                                mysize = tb.CalculateTextSize().Area();
                                break;
                            }
                            if (lookat.Parent == null)
                            {
                                mysize = uiComponent.Resolution.X * uiComponent.Resolution.Y;
                                break;
                            }
                            lookat = lookat.Parent;
                        } while (true);
                        // ok, use this size info to select the smallest element
                        if (mysize < smallestArea)
                        {
                            smallestArea = mysize;
                            elementId    = thisElementId;
                        }
                    }
                }
            }
            return(smallestArea < float.MaxValue);
        }
Example #5
0
        private void SetCurrentSelection(AbsoluteId elementId)
        {
            var element = FindPartViewModel(elementId);

            if (element == null)
            {
                return;
            }

            ClearSelection();

            SelectedContent.Add(element);
        }
        public async Task RegisterReferencer(AbsoluteId referencerId)
        {
            gameDispatcher.EnsureAccess();
            using (await loader.LockDatabaseAsynchronously())
            {
                if (references.ContainsKey(referencerId))
                {
                    throw new InvalidOperationException("The given referencer is already registered.");
                }

                references.Add(referencerId, new Dictionary <AssetId, List <ReferenceAccessor> >());
            }
        }
Example #7
0
        protected override void UpdateNode(IAssetNodePresenter node)
        {
            var entity = node.Root.Value as Entity;

            if (node.Asset?.Editor == null || entity == null)
            {
                return;
            }

            var partId    = new AbsoluteId(node.Asset.Id, entity.Id);
            var viewModel = (EntityHierarchyElementViewModel)((EntityHierarchyEditorViewModel)node.Asset.Editor).FindPartViewModel(partId);

            viewModel?.UpdateNodePresenter(node);
        }
Example #8
0
        protected override void FinalizeTree(IAssetNodePresenter root)
        {
            var entity = root.Value as Entity;

            if (root.Asset?.Editor == null || entity == null)
            {
                return;
            }

            var partId    = new AbsoluteId(root.Asset.Id, entity.Id);
            var viewModel = (EntityHierarchyElementViewModel)((EntityHierarchyEditorViewModel)root.Asset.Editor).FindPartViewModel(partId);

            viewModel?.FinalizeNodePresenterTree(root);
            base.FinalizeTree(root);
        }
        /// <inheritdoc/>
        void IEditorGameSelectionViewModelService.RemoveSelectable(AbsoluteId id)
        {
            Editor.Controller.EnsureAssetAccess();
            Editor.Controller.InvokeAsync(() => SelectableIds.Remove(id));
            // Remove from game selection, in case it was selected
            var element = (EntityHierarchyElementViewModel)Editor.FindPartViewModel(id);

            if (element == null)
            {
                return;
            }
            // Retrieve old selection to pass it to the event
            var oldSelectionIds = GetSelectedRootIds();

            RemoveFromSelection(element);
            RaiseSelectionUpdated(oldSelectionIds);
        }
Example #10
0
        /// <inheritdoc />
        public override Task RemovePart([NotNull] UIHierarchyItemViewModel parent, UIElement assetSidePart)
        {
            EnsureAssetAccess();

            return(InvokeAsync(() =>
            {
                Logger.Debug($"Removing element {assetSidePart.Id} from game-side scene");
                var partId = new AbsoluteId(AssetId.Empty, assetSidePart.Id);
                var part = (UIElement)FindPart(partId);
                if (part == null)
                {
                    throw new InvalidOperationException($"The given {nameof(assetSidePart.Id)} does not correspond to any existing part.");
                }

                var parentId = (parent as UIElementViewModel)?.Id;
                if (parentId == null)
                {
                    RootElements.Remove(assetSidePart.Id);
                }
                else
                {
                    var parentElement = (UIElement)FindPart(parentId.Value);
                    if (parentElement == null)
                    {
                        throw new InvalidOperationException($"The given {nameof(parentId)} does not correspond to any existing part.");
                    }

                    var panel = parentElement as Panel;
                    var contentControl = parentElement as ContentControl;
                    if (panel != null)
                    {
                        var i = panel.Children.IndexOf(part);
                        GameSideNodeContainer.GetNode(panel.Children).Remove(part, new NodeIndex(i));
                    }
                    else if (contentControl != null)
                    {
                        if (contentControl.Content != null)
                        {
                            throw new InvalidOperationException($"The control corresponding to the given {nameof(parentId)} is a ContentControl that already has a Content.");
                        }
                        GameSideNodeContainer.GetNode(contentControl)[nameof(contentControl.Content)].Update(null);
                    }
                }
            }));
        }
Example #11
0
        private async Task Initialize([NotNull] TAssetPart assetSideInstance)
        {
            var partId           = new AbsoluteId(Owner.Id.AssetId, assetSideInstance.Id);
            var gameSideInstance = await Editor.Controller.InvokeAsync(() => Editor.Controller.FindGameSidePart(partId));

            if (gameSideInstance == null)
            {
                throw new InvalidOperationException("Unable to reach the game-side instance");
            }

            using (await DispatcherLock.Lock(true, Editor.Controller, Editor.Dispatcher))
            {
                Editor.Controller.Logger.Debug($"Initial linking for part {assetSideInstance.Id}");
                var assetNode = (IAssetNode)Editor.NodeContainer.GetNode(assetSideInstance);
                var gameNode  = Editor.Controller.GameSideNodeContainer.GetOrCreateNode(gameSideInstance);
                gameObjectLinker.LinkGraph(assetNode, gameNode);
            }
        }
        public async Task PushContentReference(AbsoluteId referencerId, AssetId contentId, IGraphNode contentNode, NodeIndex index)
        {
            gameDispatcher.EnsureAccess();
            using (await loader.LockDatabaseAsynchronously())
            {
                if (!references.ContainsKey(referencerId))
                {
                    throw new InvalidOperationException("The given referencer is not registered.");
                }

                var referencer = references[referencerId];
                List <ReferenceAccessor> accessors;
                if (!referencer.TryGetValue(contentId, out accessors))
                {
                    accessors             = new List <ReferenceAccessor>();
                    referencer[contentId] = accessors;
                }
                var accessor = new ReferenceAccessor(contentNode, index);
                if (accessors.Contains(accessor))
                {
                    // If the reference already exists, clear it and re-enter
                    await ClearContentReference(referencerId, contentId, contentNode, index);
                    await PushContentReference(referencerId, contentId, contentNode, index);

                    return;
                }

                accessors.Add(accessor);

                object value;
                if (contents.TryGetValue(contentId, out value))
                {
                    accessor.Update(value);
                }
                else
                {
                    // Build only if not requested yet (otherwise we just need to wait for ReplaceContent() to be called, it will also replace this reference since it was added just before)
                    if (buildPending.Add(contentId))
                    {
                        loader.BuildAndReloadAsset(contentId);
                    }
                }
            }
        }
        /// <inheritdoc/>
        void IEditorGameSelectionViewModelService.AddSelectable(AbsoluteId id)
        {
            Editor.Controller.EnsureAssetAccess();
            Editor.Controller.InvokeAsync(() => SelectableIds.Add(id));
            // Add to game selection, in case it is already selected
            var element = (EntityHierarchyElementViewModel)Editor.FindPartViewModel(id);

            if (element == null)
            {
                return;
            }
            if (Editor.SelectedContent.Contains(element))
            {
                // Retrieve old selection to pass it to the event
                var oldSelectionIds = GetSelectedRootIds();
                AddToSelection(element);
                RaiseSelectionUpdated(oldSelectionIds);
            }
        }
Example #14
0
        private void FocusOnEntity()
        {
            if (!IsLoaded)
            {
                return;
            }

            int meshIndex = -1;
            var target    = this;
            var result    = Editor.Controller.GetService <IEditorGameMaterialHighlightViewModelService>()?.GetTargetMeshIndex(this);

            if (result != null)
            {
                var partId = new AbsoluteId(Asset.Id, result.Item1);
                target    = (EntityViewModel)Editor.FindPartViewModel(partId);
                meshIndex = result.Item2;
            }

            Editor.Controller.GetService <IEditorGameEntityCameraViewModelService>().CenterOnEntity(target, meshIndex);
        }
Example #15
0
        private async void ContentChanged(object sender, MemberNodeChangeEventArgs e)
        {
            var oldElement = e.OldValue as UIElement;
            var newElement = e.NewValue as UIElement;

            switch (e.ChangeType)
            {
            case ContentChangeType.ValueChange:
                if (oldElement != null)
                {
                    var partId    = new AbsoluteId(Asset.Id, oldElement.Id);
                    var viewModel = (UIElementViewModel)Editor.FindPartViewModel(partId);
                    RemoveChildViewModel(viewModel);
                }
                if (newElement != null)
                {
                    var elementDesign = UIAsset.Hierarchy.Parts[newElement.Id];
                    var viewModel     = (UIElementViewModel)Editor.CreatePartViewModel(Asset, elementDesign);
                    AddItem(viewModel);

                    // Collect children that need to be notified
                    var childrenToNotify = viewModel.Children.BreadthFirst(x => x.Children).ToList();
                    await Editor.Controller.AddPart(this, viewModel.AssetSideUIElement);

                    // Manually notify the game-side scene
                    viewModel.NotifyGameSidePartAdded().Forget();
                    foreach (var child in childrenToNotify)
                    {
                        child.NotifyGameSidePartAdded().Forget();
                    }
                }
                else
                {
                    Editor.Controller.InvokeAsync(() => ((ContentControl)Editor.Controller.FindGameSidePart(Id)).Content = null).Forget();
                }
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
Example #16
0
        public void Select(Guid elementId, bool isAdditive)
        {
            var partId  = new AbsoluteId(Asset.Id, elementId);
            var element = FindPartViewModel(partId);

            if (element == null)
            {
                return;
            }

            if (SelectedContent.Contains(element))
            {
                return;
            }

            if (!isAdditive)
            {
                ClearSelection();
            }
            SelectedContent.Add(element);
        }
Example #17
0
        public void UpdateProperties(Guid elementId, IReadOnlyDictionary <string, object> changes)
        {
            var partId  = new AbsoluteId(Asset.Id, elementId);
            var element = (UIElementViewModel)FindPartViewModel(partId);

            if (element == null)
            {
                return;
            }

            var node = NodeContainer.GetNode(element.AssetSideUIElement);

            if (node == null)
            {
                return;
            }

            using (var transaction = UndoRedoService.CreateTransaction())
            {
                foreach (var kv in changes)
                {
                    var propertyName  = kv.Key;
                    var propertyValue = kv.Value;

                    var member = node.TryGetChild(propertyName);
                    if (member == null)
                    {
                        continue;
                    }

                    // Update properties only when they actually changed
                    var currentValue = member.Retrieve();
                    if (currentValue != propertyValue)
                    {
                        member.Update(propertyValue);
                    }
                }
                UndoRedoService.SetName(transaction, $"Update {element.ElementType.Name}");
            }
        }
        public async Task ClearContentReference(AbsoluteId referencerId, AssetId contentId, IGraphNode contentNode, NodeIndex index)
        {
            gameDispatcher.EnsureAccess();
            using (await loader.LockDatabaseAsynchronously())
            {
                if (!references.ContainsKey(referencerId))
                {
                    throw new InvalidOperationException("The given referencer is not registered.");
                }

                var referencer = references[referencerId];
                if (!referencer.ContainsKey(contentId))
                {
                    throw new InvalidOperationException("The given content is not registered to the given referencer.");
                }

                var accessors    = referencer[contentId];
                var accessor     = new ReferenceAccessor(contentNode, index);
                var accesorIndex = accessors.IndexOf(accessor);
                if (accesorIndex < 0)
                {
                    throw new InvalidOperationException("The given reference is not registered for the given content and referencer.");
                }

                accessors.RemoveAt(accesorIndex);
                if (accessors.Count == 0)
                {
                    referencer.Remove(contentId);
                    // Unload the content if nothing else is referencing it anymore
                    var unloadContent = references.Values.SelectMany(x => x.Keys).All(x => x != contentId);
                    if (unloadContent)
                    {
                        await loader.UnloadAsset(contentId);

                        contents.Remove(contentId);
                    }
                }
            }
        }
        protected override async Task <bool> PropagatePartReference(IGraphNode gameSideNode, object value, INodeChangeEventArgs e)
        {
            var component = value as EntityComponent;
            // Don't propagate if we're updating the EntityComponentCollection (not a reference),
            // or if we're updating TransformComponent.Children (handled as a part addition/removal)
            var nodeIndex = (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty;

            if (component != null && e.Node.Type != typeof(EntityComponentCollection) && !((EntityHierarchyPropertyGraph)Owner.Asset.PropertyGraph).IsChildPartReference(e.Node, nodeIndex))
            {
                var index  = component.Entity.Components.IndexOf(component);
                var partId = new AbsoluteId(Owner.Id.AssetId, component.Entity.Id); // FIXME: what about cross-asset references?
                await Editor.Controller.InvokeAsync(() =>
                {
                    var gameSideEntity    = (Entity)Editor.Controller.FindGameSidePart(partId);
                    var gameSideComponent = gameSideEntity?.Components[index];
                    UpdateGameSideContent(gameSideNode, gameSideComponent, e.ChangeType, nodeIndex);
                });

                return(true);
            }
            return(await base.PropagatePartReference(gameSideNode, value, e));
        }
        public async Task RemoveReferencer(AbsoluteId referencerId)
        {
            gameDispatcher.EnsureAccess();
            using (await loader.LockDatabaseAsynchronously())
            {
                if (!references.ContainsKey(referencerId))
                {
                    throw new InvalidOperationException("The given referencer is not registered.");
                }

                var referencer = references[referencerId];
                // Properly clear all reference first
                foreach (var content in referencer.ToDictionary(x => x.Key, x => x.Value))
                {
                    foreach (var reference in content.Value.ToList())
                    {
                        // Ok to await in the loop, Clear should never yield because we already own the lock.
                        await reference.Clear(this, referencerId, content.Key);
                    }
                }
                references.Remove(referencerId);
            }
        }
Example #21
0
 protected abstract object FindPart(AbsoluteId partId);
Example #22
0
 /// <inheritdoc/>
 public object FindGameSidePart(AbsoluteId partId)
 {
     EnsureNotDestroyed();
     EnsureGameAccess();
     return(FindPart(partId));
 }
Example #23
0
 /// <inheritdoc/>
 protected override object FindPart(AbsoluteId id)
 {
     return(Game.FindSubEntity(Guid.Empty, id.ObjectId));
 }
 public Task Clear([NotNull] LoaderReferenceManager manager, AbsoluteId referencerId, AssetId contentId)
 {
     return(manager.ClearContentReference(referencerId, contentId, contentNode, index));
 }
Example #25
0
 public abstract IEditorGamePartViewModel FindPartViewModel(AbsoluteId id);
Example #26
0
        public EntityFolderOperation([NotNull] EntityHierarchyViewModel asset, Action action, [NotNull] string folderPath, AbsoluteId ownerId)
            : this(action, folderPath, ownerId, asset.SafeArgument(nameof(asset)).Dirtiables)
        {
            if (action != Action.FolderCreated && action != Action.FolderDeleted)
            {
                throw new ArgumentException(@"The action must be FolderCreated or FolderDeleted when using this constructor.", nameof(action));
            }

            this.asset = asset;
        }
Example #27
0
 protected GraphicsCompositorItemViewModel([NotNull] GraphicsCompositorEditorViewModel editor) : base(editor.SafeArgument(nameof(editor)).ServiceProvider)
 {
     Editor = editor;
     Id     = new AbsoluteId(editor.Asset.Id, Guid.NewGuid());
 }
Example #28
0
 protected virtual object GetObjectToSelect(AbsoluteId id)
 {
     return(FindPartViewModel(id));
 }
Example #29
0
 /// <summary>
 /// Finds the game-side <see cref="UIElement"/> corresponding to the given <paramref name="elementId"/>.
 /// </summary>
 /// <remarks>
 /// This method will search through all roots. If the root is known , consider calling <see cref="FindElement(System.Guid,System.Guid)"/> instead.
 /// </remarks>
 /// <param name="elementId">The identifier of the game-side element to find.</param>
 /// <returns>A game-side element corresponding to the given id if found, <c>null</c> otherwise.</returns>
 protected override object FindPart(AbsoluteId elementId)
 {
     return(RootElements.Keys.Select(rootId => FindElement(rootId, elementId.ObjectId)).FirstOrDefault(x => x != null));
 }
Example #30
0
        private async void TransformChildrenChanged(object sender, ItemChangeEventArgs e)
        {
            Entity childEntity;

            switch (e.ChangeType)
            {
            case ContentChangeType.CollectionAdd:
                childEntity = ((TransformComponent)e.NewValue).Entity;
                Editor.Logger.Verbose($"Add {childEntity.Name} ({childEntity.Id}) to the Entities collection");
                break;

            case ContentChangeType.CollectionRemove:
                childEntity = ((TransformComponent)e.OldValue).Entity;
                Editor.Logger.Verbose($"Remove {childEntity.Name} ({childEntity.Id}) from the Entities collection");
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            // Update view model
            if (e.ChangeType == ContentChangeType.CollectionAdd)
            {
                // TODO: can be factorized with what EntityHierarchyRootViewModel is doing, through a base method. There are a few differences though.
                var component = (TransformComponent)e.NewValue;
                var entity    = EntityHierarchy.Hierarchy.Parts[component.Entity.Id];
                var element   = (EntityViewModel)Editor.CreatePartViewModel(Asset, entity);
                var index     = e.Index.Int;
                var parent    = FindOrCreateFolder(entity.Folder);
                parent.InsertEntityViewModel(element, parent.FindIndexInParent(index, EntityDesign.Entity.Transform.Children.Select(x => EntityHierarchy.Hierarchy.Parts[x.Entity.Id])));

                // FIXME: when we allow loading/unloading single entities, this should be implemented in OnLoadingRequested. Then just call RequestLoading(true).
                // Check if the container root is loaded
                if (EntityHierarchyEditorViewModel.GetRoot(this).IsLoaded)
                {
                    var children = element.TransformChildren.BreadthFirst(x => x.TransformChildren).ToList();
                    // Set the new entity and its children as 'loading'
                    element.IsLoading = true;
                    foreach (var child in children)
                    {
                        child.IsLoading = true;
                    }
                    // Add the entity to the game (actually load the entity and its children)
                    await Editor.Controller.AddPart(this, element.AssetSideEntity);

                    // Set the new entity and its children as 'loaded'
                    element.IsLoaded = true;
                    foreach (var child in children)
                    {
                        child.IsLoaded = true;
                    }
                    // Manually notify the game-side scene
                    element.NotifyGameSidePartAdded().Forget();
                    foreach (var child in children)
                    {
                        child.NotifyGameSidePartAdded().Forget();
                    }
                }
            }
            else if (e.ChangeType == ContentChangeType.CollectionRemove)
            {
                var component = (TransformComponent)e.OldValue;
                var partId    = new AbsoluteId(Asset.Id, component.Entity.Id);
                var element   = (EntityViewModel)Editor.FindPartViewModel(partId);
                if (element == null)
                {
                    throw new InvalidOperationException($"{nameof(element)} cannot be null");
                }
                RemoveEntityViewModel(element);
                // FIXME: when we allow loading/unloading single entities, this should be implemented in OnLoadingRequested. Then just call RequestLoading(false).
                if (element.IsLoaded)
                {
                    element.IsLoading = true;
                    Editor.Controller.RemovePart(this, element.AssetSideEntity).Forget();
                    element.IsLoaded = false;
                }
            }
        }