Skip to content
Permalink
Browse files

[GameStudio] Improve "Add component" button usability. (#411)

  • Loading branch information
dfkeenan authored and xen2 committed Apr 13, 2019
1 parent fdad9d8 commit a569a2056885092736f745838e147dcb4bb685b6
Showing with 717 additions and 69 deletions.
  1. +17 −0 sources/core/Xenko.Core/Extensions/EnumerableExtensions.cs
  2. +98 −0 sources/editor/Xenko.Assets.Presentation/NodePresenters/Commands/AddNewScriptComponentCommand.cs
  3. +4 −0 sources/editor/Xenko.Assets.Presentation/NodePresenters/Keys/EntityHierarchyData.cs
  4. +10 −1 sources/editor/Xenko.Assets.Presentation/NodePresenters/Updaters/EntityHierarchyAssetNodeUpdater.cs
  5. +4 −1 sources/editor/Xenko.Assets.Presentation/Templates/ScriptNameWindow.xaml
  6. +18 −1 sources/editor/Xenko.Assets.Presentation/Templates/ScriptNameWindow.xaml.cs
  7. +49 −1 sources/editor/Xenko.Assets.Presentation/Templates/ScriptTemplateGenerator.cs
  8. +168 −0 sources/editor/Xenko.Assets.Presentation/View/AddEntityComponentUserControl.xaml
  9. +142 −0 sources/editor/Xenko.Assets.Presentation/View/AddEntityComponentUserControl.xaml.cs
  10. +7 −59 sources/editor/Xenko.Assets.Presentation/View/EntityPropertyTemplates.xaml
  11. +15 −0 sources/editor/Xenko.Assets.Presentation/View/ImageDictionary.xaml
  12. +2 −0 sources/editor/Xenko.Assets.Presentation/XenkoDefaultAssetsPlugin.cs
  13. +79 −0 sources/editor/Xenko.Core.Assets.Editor/Quantum/NodePresenters/Commands/AbstractNodeTypeGroup.cs
  14. +11 −3 sources/editor/Xenko.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs
  15. +1 −0 sources/engine/Xenko.Engine/Engine/AnimationComponent.cs
  16. +1 −0 sources/engine/Xenko.Engine/Engine/AudioEmitterComponent.cs
  17. +1 −0 sources/engine/Xenko.Engine/Engine/AudioListenerComponent.cs
  18. +51 −0 sources/engine/Xenko.Engine/Engine/ComponentCategoryAttribute.cs
  19. +1 −0 sources/engine/Xenko.Engine/Engine/EntityComponent.cs
  20. +1 −0 sources/engine/Xenko.Engine/Engine/LightComponent.cs
  21. +1 −0 sources/engine/Xenko.Engine/Engine/LightProbeComponent.cs
  22. +1 −0 sources/engine/Xenko.Engine/Engine/LightShaftBoundingVolumeComponent.cs
  23. +1 −0 sources/engine/Xenko.Engine/Engine/LightShaftComponent.cs
  24. +1 −0 sources/engine/Xenko.Engine/Engine/ModelComponent.cs
  25. +1 −0 sources/engine/Xenko.Engine/Engine/ModelNodeLinkComponent.cs
  26. +1 −0 sources/engine/Xenko.Engine/Engine/ScriptComponent.cs
  27. +1 −0 sources/engine/Xenko.Engine/Engine/SpriteComponent.cs
  28. +1 −0 sources/engine/Xenko.Navigation/NavigationBoundingBoxComponent.cs
  29. +1 −0 sources/engine/Xenko.Navigation/NavigationComponent.cs
  30. +1 −0 sources/engine/Xenko.Particles/Components/ParticleSystemComponent.cs
  31. +1 −0 sources/engine/Xenko.Physics/Engine/PhysicsComponent.cs
  32. +1 −0 sources/engine/Xenko.SpriteStudio.Runtime/SpriteStudioComponent.cs
  33. +1 −0 sources/engine/Xenko.SpriteStudio.Runtime/SpriteStudioNodeLinkComponent.cs
  34. +1 −0 sources/engine/Xenko.UI/Engine/UIComponent.cs
  35. +1 −0 sources/engine/Xenko.UI/Engine/UIElementLinkComponent.cs
  36. +1 −0 sources/engine/Xenko.Video/VideoComponent.cs
  37. +2 −2 sources/presentation/Xenko.Core.Presentation/Behaviors/ToggleButtonPopupBehavior.cs
  38. +19 −1 sources/presentation/Xenko.Core.Presentation/Controls/FilteringComboBox.cs
@@ -149,5 +149,22 @@ internal static IEnumerable<LinkedListNode<T>> EnumerateNodes<T>([NotNull] this
node = node.Next;
}
}

/// <summary>
/// Calculates a combined hash code for items of the enumerbale.
/// </summary>
/// <typeparam name="T">Generic type parameter.</typeparam>
/// <param name="source">Input enumerable to work on.</param>
/// <returns>A combined hash code or 0 if the source is empty.</returns>
[Pure]
public static int ToHashCode<T>(this IEnumerable<T> source) where T : class
{
if (source.IsNullOrEmpty()) return 0;

unchecked
{
return source.Aggregate(17, (hash, item) => hash * 23 + item.GetHashCode());
}
}
}
}
@@ -0,0 +1,98 @@
// Copyright (c) Xenko contributors (https://xenko.com)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Linq;
using Xenko.Core.Annotations;
using Xenko.Core.Reflection;
using Xenko.Core.Presentation.Quantum;
using Xenko.Core.Presentation.Quantum.Presenters;
using Xenko.Core.Quantum;
using System.Collections.Generic;
using Xenko.Core.Assets.Templates;
using Xenko.Core.Assets.Editor.Components.TemplateDescriptions.ViewModels;
using Xenko.Core.Presentation.Services;
using Xenko.Core.Assets.Editor.Quantum.NodePresenters;
using Xenko.Core.Assets;
using Xenko.Assets.Presentation.Templates;
using Xenko.Core;
using Xenko.Engine;
using Xenko.Assets.Presentation.ViewModel;
using Xenko.Core.Assets.Editor.Components.Properties;
using Xenko.Assets.Presentation.AssetEditors;
using Xenko.Core.Presentation.Dirtiables;

namespace Xenko.Assets.Presentation.NodePresenters.Commands
{
public class AddNewScriptComponentCommand : SyncNodePresenterCommandBase
{
/// <summary>
/// The name of this command.
/// </summary>
public const string CommandName = "AddNewScriptComponent";

/// <inheritdoc/>
public override string Name => CommandName;

/// <inheritdoc/>
public override CombineMode CombineMode => CombineMode.CombineOnlyForAll;

/// <inheritdoc/>
public override bool CanAttach(INodePresenter nodePresenter)
{
return typeof(EntityComponentCollection).IsAssignableFrom(nodePresenter.Descriptor.Type);
}

/// <inheritdoc/>
protected override async void ExecuteSync(INodePresenter nodePresenter, object parameter, object preExecuteResult)
{
if (!(nodePresenter is AssetMemberNodePresenter assetPresenter))
return;

var undoRedoService = assetPresenter.Asset.UndoRedoService;
var session = assetPresenter.Asset.Session;
var serviceProvider = assetPresenter.Asset.ServiceProvider;

var scriptSourceCodeProvider = serviceProvider.TryGet<IScriptSourceCodeResolver>();

if (scriptSourceCodeProvider == null)
return;

var template = ScriptTemplateGenerator.GetScriptTemplateAssetDescriptions(session.FindTemplates(TemplateScope.Asset)).FirstOrDefault();

if (template == null)
return;

var viewModel = new TemplateDescriptionViewModel(serviceProvider, template);
var customParameters = ScriptTemplateGenerator.GetAssetOverrideParameters(parameter as string, true);
var assetViewModel = (await session.ActiveAssetView.RunAssetTemplate(viewModel, null, customParameters)).FirstOrDefault();

if (assetViewModel == null)
return;

//TODO: Maybe situations where this asset/node are no longer valid.
if (assetViewModel.IsDeleted)
{
return;
}

IEnumerable<Type> componentTypes = scriptSourceCodeProvider.GetTypesFromSourceFile(assetViewModel.AssetItem.FullPath);
var componentType = componentTypes.FirstOrDefault();
if (componentType != null)
{
using (var transaction = session.UndoRedoService.CreateTransaction())
{
object component = Activator.CreateInstance(componentType);
var index = new Index(nodePresenter.Children.Count);
nodePresenter.AddItem(component);
session.UndoRedoService.PushOperation(
new AnonymousDirtyingOperation(
assetPresenter.Asset.Dirtiables,
() => nodePresenter.RemoveItem(component, index),
() => nodePresenter.AddItem(component)));

session.UndoRedoService.SetName(transaction, "Add new script component.");
}
}
}
}
}
@@ -13,5 +13,9 @@ public static class EntityHierarchyData
{
public const string EntityComponentAvailableTypes = nameof(EntityComponentAvailableTypes);
public static readonly PropertyKey<IEnumerable<AbstractNodeType>> EntityComponentAvailableTypesKey = new PropertyKey<IEnumerable<AbstractNodeType>>(EntityComponentAvailableTypes, typeof(EntityHierarchyData), new PropertyCombinerMetadata(AbstractNodeEntryData.CombineProperty));


public const string EntityComponentAvailableTypeGroups = nameof(EntityComponentAvailableTypeGroups);
public static readonly PropertyKey<IEnumerable<AbstractNodeTypeGroup>> EntityComponentAvailableTypeGroupsKey = new PropertyKey<IEnumerable<AbstractNodeTypeGroup>>(EntityComponentAvailableTypeGroups, typeof(EntityHierarchyData), new PropertyCombinerMetadata(AbstractNodeEntryData.CombineProperty));
}
}
@@ -14,6 +14,7 @@
using Xenko.Assets.Presentation.NodePresenters.Keys;
using Xenko.Assets.Presentation.ViewModel;
using Xenko.Engine;
using Xenko.Core.Presentation.Core;

namespace Xenko.Assets.Presentation.NodePresenters.Updaters
{
@@ -50,9 +51,17 @@ protected override void UpdateNode(IAssetNodePresenter node)
(EntityComponentAttributes.Get(x).AllowMultipleComponents
|| ((EntityComponentCollection)node.Value).All(y => y.GetType() != x)))
.OrderBy(DisplayAttribute.GetDisplayName)
.Select(x => new AbstractNodeType(x));
.Select(x => new AbstractNodeType(x)).ToArray();
node.AttachedProperties.Add(EntityHierarchyData.EntityComponentAvailableTypesKey, types);

//TODO: Choose a better grouping method.
var typeGroups =
types.GroupBy(t => ComponentCategoryAttribute.GetCategory(t.Type))
.OrderBy(g => g.Key)
.Select(g => new AbstractNodeTypeGroup(g.Key, g.ToArray())).ToArray();

node.AttachedProperties.Add(EntityHierarchyData.EntityComponentAvailableTypeGroupsKey, typeGroups);

// Cannot replace entity component collection.
var replaceCommandIndex = node.Commands.IndexOf(x => x.Name == ReplacePropertyCommand.CommandName);
if (replaceCommandIndex >= 0)
@@ -1,4 +1,4 @@
<xk:ModalWindow x:Class="Xenko.Assets.Presentation.Templates.ScriptNameWindow"
<xk:ModalWindow x:Class="Xenko.Assets.Presentation.Templates.ScriptNameWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -25,6 +25,9 @@
<TextBlock Text="{xk:Localize Class:}" Margin="10,0,0,0" VerticalAlignment="Center"/>
<xk:TextBox x:Name="ClassNameTextBox" Width="200" Margin="10" KeyDown="TextBoxKeyDown" GetFocusOnLoad="True" SelectAllOnFocus="True"/>

<TextBlock Text="{xk:Localize Template:}" Margin="10,0,0,0" VerticalAlignment="Center" Visibility="{Binding Visibility, ElementName= TemplateComboBox}"/>
<ComboBox x:Name="TemplateComboBox" Width="200" Margin="10" DisplayMemberPath="Name" />

<TextBlock Text="{xk:Localize Namespace:}" Margin="10,0,0,0" VerticalAlignment="Center"/>
<xk:TextBox x:Name="NamespaceTextBox" Width="200" Margin="10" KeyDown="TextBoxKeyDown" SelectAllOnFocus="True"/>
</xk:KeyValueGrid>
@@ -13,6 +13,7 @@
using MessageBoxButton = Xenko.Core.Presentation.Services.MessageBoxButton;
using Xenko.Core.Extensions;
using Xenko.Core.Translation;
using Xenko.Core.Assets.Templates;

namespace Xenko.Assets.Presentation.Templates
{
@@ -23,6 +24,8 @@ public partial class ScriptNameWindow
{
private readonly ViewModelServiceProvider services;
private static readonly List<string> ReservedNames = new List<string>();
private readonly bool enableTemplateSelect;
private readonly TemplateAssetDescription defaultScriptTemplate;

static ScriptNameWindow()
{
@@ -39,23 +42,36 @@ static ScriptNameWindow()
}
}

public ScriptNameWindow(string defaultClassName, string defaultNamespace)
public ScriptNameWindow(string defaultClassName, string defaultNamespace, TemplateAssetDescription defaultScriptTemplate, bool enableTemplateSelect, IEnumerable<TemplateAssetDescription> scriptTemplates)
{
var dispatcher = new DispatcherService(Dispatcher);
services = new ViewModelServiceProvider(new object[] { dispatcher, new DialogService(dispatcher, EditorViewModel.Instance.EditorName) });
InitializeComponent();
ClassNameTextBox.Text = defaultClassName;
NamespaceTextBox.Text = defaultNamespace;

TemplateComboBox.Visibility = enableTemplateSelect ? Visibility.Visible : Visibility.Collapsed;
if (enableTemplateSelect)
{
TemplateComboBox.ItemsSource = scriptTemplates;
TemplateComboBox.SelectedValue = defaultScriptTemplate;
}

this.enableTemplateSelect = enableTemplateSelect;
this.defaultScriptTemplate = defaultScriptTemplate;
}

public string ClassName { get; private set; }

public string Namespace { get; private set; }

public TemplateAssetDescription ScriptTemplate { get; private set; }

private async void Validate()
{
ClassName = Utilities.BuildValidClassName(ClassNameTextBox.Text, ReservedNames);
Namespace = Utilities.BuildValidNamespaceName(NamespaceTextBox.Text, ReservedNames);
ScriptTemplate = enableTemplateSelect ? TemplateComboBox.SelectedValue as TemplateAssetDescription : defaultScriptTemplate;

if (string.IsNullOrWhiteSpace(ClassName) || string.IsNullOrWhiteSpace(Namespace))
{
@@ -71,6 +87,7 @@ private void Cancel()
{
ClassName = null;
Namespace = null;
ScriptTemplate = null;
Result = Xenko.Core.Presentation.Services.DialogResult.Cancel;
Close();
}
@@ -14,19 +14,49 @@
using Xenko.Core.Translation;
using Xenko.Assets.Scripts;
using EditorViewModel = Xenko.Core.Assets.Editor.ViewModel.EditorViewModel;
using System.Linq;

namespace Xenko.Assets.Presentation.Templates
{
public class ScriptTemplateGenerator : AssetTemplateGenerator
{
private const string AssetTypeName = "ScriptSourceFileAsset";

public static readonly ScriptTemplateGenerator Default = new ScriptTemplateGenerator();

private static readonly PropertyKey<string> ClassNameKey = new PropertyKey<string>("ClassNameKey", typeof(ScriptTemplateGenerator));

private static readonly PropertyKey<string> DefaultClassNameKey = new PropertyKey<string>(nameof(DefaultClassNameKey), typeof(ScriptTemplateGenerator));

private static readonly PropertyKey<bool> EnableTemplateSelectKey = new PropertyKey<bool>(nameof(EnableTemplateSelectKey), typeof(ScriptTemplateGenerator));

private static readonly PropertyKey<bool> SaveSessionKey = new PropertyKey<bool>("SaveSessionKey", typeof(ScriptTemplateGenerator));

public static void SetClassName(AssetTemplateGeneratorParameters parameters, string className) => parameters.Tags.Set(ClassNameKey, className);

public static IEnumerable<TemplateAssetDescription> GetScriptTemplateAssetDescriptions(IEnumerable<TemplateDescription> templates)
{
if (templates == null)
{
throw new ArgumentNullException(nameof(templates));
}

return templates.OfType<TemplateAssetDescription>().Where(t => t.AssetTypeName == AssetTypeName);
}

public static PropertyContainer GetAssetOverrideParameters(string defaultClassName, bool enableTemplateSelect = true)
{
var customParameters = new PropertyContainer();

if (!string.IsNullOrEmpty(defaultClassName))
{
customParameters[DefaultClassNameKey] = defaultClassName;
}

customParameters[EnableTemplateSelectKey] = enableTemplateSelect;
return customParameters;
}

public override bool IsSupportingTemplate(TemplateDescription templateDescription)
{
if (templateDescription == null) throw new ArgumentNullException(nameof(templateDescription));
@@ -42,14 +72,32 @@ protected override async Task<bool> PrepareAssetCreation(AssetTemplateGeneratorP
{
if (!parameters.Unattended)
{
var window = new ScriptNameWindow(parameters.Description.DefaultOutputName, parameters.Namespace);
string defaultClassName = parameters.Description.DefaultOutputName;

if(parameters.Tags.TryGetValue(DefaultClassNameKey, out string className) && !string.IsNullOrEmpty(className))
{
defaultClassName = className;
}

bool enableTemplateSelect = parameters.TryGetTag(EnableTemplateSelectKey);

IEnumerable<TemplateAssetDescription> scriptTemplates = null;

if (enableTemplateSelect)
{
scriptTemplates = GetScriptTemplateAssetDescriptions(TemplateManager.FindTemplates(TemplateScope.Asset, parameters.Package.Session));
}

var window = new ScriptNameWindow(defaultClassName, parameters.Namespace, parameters.Description as TemplateAssetDescription, enableTemplateSelect, scriptTemplates);

await window.ShowModal();

if (window.Result == DialogResult.Cancel)
return false;

parameters.Namespace = window.Namespace;
parameters.Tags.Set(ClassNameKey, window.ClassName);
parameters.Description = window.ScriptTemplate;

var ask = Xenko.Core.Assets.Editor.Settings.EditorSettings.AskBeforeSavingNewScripts.GetValue();
if (ask)

0 comments on commit a569a20

Please sign in to comment.
You can’t perform that action at this time.