Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static class AssetCollision
/// assetResolver
/// </exception>
/// <exception cref="System.ArgumentException">List cannot contain null items;inputItems</exception>
public static void Clean(Package package, ICollection<AssetItem> inputItems, ICollection<AssetItem> outputItems, AssetResolver assetResolver, bool cloneInput, bool removeUnloadableObjects)
public static void Clean(Package? package, ICollection<AssetItem> inputItems, ICollection<AssetItem> outputItems, AssetResolver assetResolver, bool cloneInput, bool removeUnloadableObjects)
{
ArgumentNullException.ThrowIfNull(inputItems);
ArgumentNullException.ThrowIfNull(outputItems);
Expand Down
2 changes: 1 addition & 1 deletion sources/editor/Stride.Assets.Editor.Avalonia/Module.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Assets.Presentation;
using Stride.Core.Assets.Presentation.Services;
using Stride.Editor.Avalonia.Preview.Views;
using Stride.Editor.Preview;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Reflection;
using Stride.Assets.Editor.Avalonia.Views;
using Stride.Core.Assets.Editor;
using Stride.Core.Assets.Editor.Services;
using Stride.Core.Assets.Presentation.ViewModels;
using Stride.Core.Diagnostics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,46 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sd="http://schemas.stride3d.net/xaml/presentation"
xmlns:vm="using:Stride.Assets.Editor.ViewModels"
xmlns:vm2="using:Stride.Assets.Presentation.ViewModels"
xmlns:aevm="using:Stride.Assets.Editor.ViewModels"
xmlns:apvm="using:Stride.Assets.Presentation.ViewModels"
xmlns:cpc="using:Stride.Core.Presentation.Commands"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Stride.Assets.Editor.Avalonia.Views.EntityHierarchyEditorView"
x:DataType="vm:EntityHierarchyEditorViewModel">
x:DataType="aevm:EntityHierarchyEditorViewModel">
<UserControl.Resources>
<ContextMenu x:Key="TreeViewItemContextMenu">
<Separator/>
<MenuItem Header="{sd:LocalizeString Cut, Context=Menu}"
Command="{Binding $parent[UserControl].DataContext.CutCommand, FallbackValue={x:Static cpc:DisabledCommand.Instance}}"/>
<MenuItem Header="{sd:LocalizeString Copy, Context=Menu}"
Command="{Binding $parent[UserControl].DataContext.CopyCommand, FallbackValue={x:Static cpc:DisabledCommand.Instance}}"/>
<MenuItem Header="{sd:LocalizeString Paste, Context=Menu}"
Command="{Binding $parent[UserControl].DataContext.PasteCommand, FallbackValue={x:Static cpc:DisabledCommand.Instance}}"/>
<MenuItem Header="{sd:LocalizeString Delete, Context=Menu}"
Command="{Binding $parent[UserControl].DataContext.DeleteCommand, FallbackValue={x:Static cpc:DisabledCommand.Instance}}"/>
</ContextMenu>
</UserControl.Resources>
<Grid ColumnDefinitions="*, 4, 3*">
<DockPanel Grid.Column="0">
<TreeView ItemsSource="{Binding HierarchyRoot, Mode=OneWay, Converter={sd:Yield}}"
SelectedItems="{Binding SelectedContent}"
SelectionMode="Multiple">
<Control.DataTemplates>
<!-- Default template -->
<TreeDataTemplate DataType="{x:Type vm2:EntityHierarchyItemViewModel}"
<TreeDataTemplate DataType="{x:Type apvm:EntityHierarchyItemViewModel}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</TreeDataTemplate>
<!-- Entity template -->
<TreeDataTemplate DataType="{x:Type vm2:EntityViewModel}"
<TreeDataTemplate DataType="{x:Type apvm:EntityViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<!-- TODO icon -->
<TextBlock Text="{Binding Name}" />
</StackPanel>
</TreeDataTemplate>
<!-- Scene template -->
<TreeDataTemplate DataType="{x:Type vm2:SceneRootViewModel}"
<TreeDataTemplate DataType="{x:Type apvm:SceneRootViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<!-- TODO icon -->
Expand All @@ -38,6 +52,7 @@
</Control.DataTemplates>
<StyledElement.Styles>
<Style Selector="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource TreeViewItemContextMenu}"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
</StyledElement.Styles>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Assets.Editor.Services;
using Stride.Core.Assets.Yaml;
using Stride.Engine;

namespace Stride.Assets.Editor.Components.CopyPasteProcessors;

internal sealed class EntityComponentCopyProcessor : ICopyProcessor
{
/// <inheritdoc/>
public bool Accept(Type dataType)
{
return dataType == typeof(TransformComponent) || dataType == typeof(EntityComponentCollection);
}

/// <inheritdoc/>
public bool Process(ref object data, AttachedYamlAssetMetadata metadata)
{
switch (data)
{
case TransformComponent transform:
PatchTransformComponent(transform);
return true;

case EntityComponentCollection collection:
{
var processed = false;
foreach (var t in collection.OfType<TransformComponent>())
{
PatchTransformComponent(t);
processed = true;
}
return processed;
}

default:
return false;
}

static void PatchTransformComponent(TransformComponent transform)
{
// We don't want to copy the children of a transform component
transform.Children.Clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Assets.Editor.Components.CopyPasteProcessors;
using Stride.Core.Extensions;
using Stride.Core.Quantum;
using Stride.Core.Reflection;
using Stride.Engine;

namespace Stride.Assets.Editor.Components.CopyPasteProcessors;

internal sealed class EntityComponentPasteProcessor : AssetPropertyPasteProcessor
{
/// <inheritdoc/>
public override bool Accept(Type targetRootType, Type targetMemberType, Type pastedDataType)
{
return (targetMemberType == typeof(EntityComponent) || targetMemberType == typeof(EntityComponentCollection)) &&
typeof(EntityComponent).IsAssignableFrom(TypeDescriptorFactory.Default.Find(pastedDataType).GetInnerCollectionType());
}

/// <inheritdoc/>
protected override bool CanRemoveItem(IObjectNode collection, NodeIndex index)
{
if (collection.Type != typeof(EntityComponentCollection))
return base.CanRemoveItem(collection, index);

// Cannot remove the transform component
if (index.Int == 0)
return false;

return base.CanRemoveItem(collection, index);
}

/// <inheritdoc/>
protected override bool CanInsertItem(IObjectNode collection, NodeIndex index, object? newItem)
{
if (collection.Type != typeof(EntityComponentCollection))
return base.CanInsertItem(collection, index, newItem);

if (newItem == null)
return false;

var componentType = newItem.GetType();
if (!EntityComponentAttributes.Get(componentType).AllowMultipleComponents)
{
// Cannot insert components that disallow multiple components
var components = (EntityComponentCollection)collection.Retrieve();
if (components.Any(x => x.GetType() == componentType))
return false;
}
return base.CanInsertItem(collection, index, newItem);
}

/// <inheritdoc/>
protected override bool CanReplaceItem(IObjectNode collection, NodeIndex index, object? newItem)
{
if (collection.Type != typeof(EntityComponentCollection))
return base.CanReplaceItem(collection, index, newItem);

if (newItem == null)
return false;

var componentType = newItem.GetType();
// Cannot replace the transform component by another type of component
if (collection.IndexedTarget(index).Type == typeof(TransformComponent) && componentType != typeof(TransformComponent))
return false;

if (!EntityComponentAttributes.Get(componentType).AllowMultipleComponents)
{
// Cannot replace components that disallow multiple components, unless it is that specific component we're replacing
var components = (EntityComponentCollection)collection.Retrieve();
if (components.Where((x, i) => x.GetType() == componentType && i != index.Int).Any())
return false;
}
return base.CanReplaceItem(collection, index, newItem);
}

/// <inheritdoc/>
protected override void ReplaceItem(IObjectNode collection, NodeIndex index, object? newItem)
{
// If we're replacing the transform component, only manually copy allowed properties to the existing one.
if (collection.Type == typeof(EntityComponentCollection) && newItem is TransformComponent newTransform)
{
var node = collection.IndexedTarget(index);
node[nameof(TransformComponent.Position)].Update(newTransform.Position);
node[nameof(TransformComponent.Rotation)].Update(newTransform.Rotation);
node[nameof(TransformComponent.Scale)].Update(newTransform.Scale);
return;
}
base.ReplaceItem(collection, index, newItem);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Assets.Entities;
using Stride.Assets.Presentation.Quantum;
using Stride.Core.Assets.Quantum;
using Stride.Core.Assets;
using Stride.Core.Quantum;
using Stride.Core;
using Stride.Core.Assets.Editor.Components.CopyPasteProcessors;
using Stride.Core.Assets.Editor.Services;
using Stride.Engine;

namespace Stride.Assets.Editor.Components.CopyPasteProcessors;

internal sealed class EntityHierarchyPasteProcessor : AssetCompositeHierarchyPasteProcessor<EntityDesign, Entity>
{
public static readonly PropertyKey<string> TargetFolderKey = new("TargetFolder", typeof(EntityHierarchyPasteProcessor));

public override Task Paste(IPasteItem pasteResultItem, AssetPropertyGraph assetPropertyGraph, ref NodeAccessor nodeAccessor, ref PropertyContainer propertyContainer)
{
if (pasteResultItem == null) throw new ArgumentNullException(nameof(pasteResultItem));

var propertyGraph = (EntityHierarchyPropertyGraph)assetPropertyGraph;
var parentEntity = nodeAccessor.RetrieveValue() as Entity;
propertyContainer.TryGetValue(TargetFolderKey, out var targetFolder);

if (pasteResultItem.Data is AssetCompositeHierarchyData<EntityDesign, Entity> hierarchy)
{
foreach (var rootEntity in hierarchy.RootParts)
{
var insertIndex = parentEntity?.Transform.Children.Count ?? propertyGraph.Asset.Hierarchy.RootParts.Count;
var entityDesign = hierarchy.Parts[rootEntity.Id];
var folder = targetFolder;
if (!string.IsNullOrEmpty(entityDesign.Folder))
{
if (!string.IsNullOrEmpty(targetFolder))
folder = folder + "/" + entityDesign.Folder;
else
folder = entityDesign.Folder;
}
entityDesign.Folder = folder ?? string.Empty;
propertyGraph.AddPartToAsset(hierarchy.Parts, entityDesign, parentEntity, insertIndex);
}
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Assets.Entities;
using Stride.Core.Assets.Editor.Components.CopyPasteProcessors;

namespace Stride.Assets.Editor.Components.CopyPasteProcessors;

internal sealed class ScenePostPasteProcessor : AssetPostPasteProcessorBase<SceneAsset>
{
/// <inheritdoc />
protected override void PostPasteDeserialization(SceneAsset asset)
{
// Clear all references (for now)
asset.Parent = null;
asset.ChildrenIds.Clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Assets.Presentation.Quantum;
using Stride.Assets.UI;
using Stride.Core.Assets.Quantum;
using Stride.Core.Assets;
using Stride.Core.Quantum;
using Stride.Core;
using Stride.Core.Assets.Editor.Components.CopyPasteProcessors;
using Stride.Core.Assets.Editor.Services;
using Stride.UI.Panels;
using Stride.UI;

namespace Stride.Assets.Editor.Components.CopyPasteProcessors;

internal sealed class UIHierarchyPasteProcessor : AssetCompositeHierarchyPasteProcessor<UIElementDesign, UIElement>
{
public override Task Paste(IPasteItem pasteResultItem, AssetPropertyGraph assetPropertyGraph, ref NodeAccessor nodeAccessor, ref PropertyContainer container)
{
if (pasteResultItem == null) throw new ArgumentNullException(nameof(pasteResultItem));

var propertyGraph = (UIAssetPropertyGraph)assetPropertyGraph;
var parentElement = nodeAccessor.RetrieveValue() as UIElement;

// 1. try to paste as hierarchy
if (pasteResultItem.Data is AssetCompositeHierarchyData<UIElementDesign, UIElement> hierarchy)
{
// Note: check that adding or inserting is supported is done in CanPaste()
foreach (var rootUIElement in hierarchy.RootParts)
{
var asset = (UIAssetBase)propertyGraph.Asset;
var insertIndex = parentElement == null ? asset.Hierarchy.RootParts.Count : ((parentElement as Panel)?.Children.Count ?? 0);
propertyGraph.AddPartToAsset(hierarchy.Parts, hierarchy.Parts[rootUIElement.Id], parentElement, insertIndex);
}
}
return Task.CompletedTask;
}
}
2 changes: 1 addition & 1 deletion sources/editor/Stride.Assets.Editor/Module.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Assets.Presentation;
using Stride.Core.Assets.Presentation.Services;

namespace Stride.Assets.Editor;

Expand Down
12 changes: 11 additions & 1 deletion sources/editor/Stride.Assets.Editor/StrideEditorPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using System.Reflection;
using Stride.Assets.Editor.Components.CopyPasteProcessors;
using Stride.Assets.Editor.Quantum.NodePresenters.Commands;
using Stride.Assets.Editor.Quantum.NodePresenters.Updaters;
using Stride.Core.Assets;
using Stride.Core.Assets.Editor;
using Stride.Core.Assets.Editor.Services;
using Stride.Core.Assets.Editor.ViewModels;
using Stride.Core.Assets.Presentation.ViewModels;
using Stride.Core.Diagnostics;
Expand Down Expand Up @@ -60,6 +61,15 @@ public override void InitializeSession(ISessionViewModel session)
//var thumbnailService = new GameStudioThumbnailService((SessionViewModel)session, settingsProvider, builderService);
//session.ServiceProvider.RegisterService(thumbnailService);

if (session.ServiceProvider.TryGet<ICopyPasteService>() is { } copyPasteService)
{
copyPasteService.RegisterProcessor(new EntityComponentCopyProcessor());
copyPasteService.RegisterProcessor(new EntityComponentPasteProcessor());
copyPasteService.RegisterProcessor(new EntityHierarchyPasteProcessor());
copyPasteService.RegisterProcessor(new UIHierarchyPasteProcessor());
copyPasteService.RegisterProcessor(new ScenePostPasteProcessor());
}

if (session is SessionViewModel sessionVm)
{
// commands
Expand Down
Loading