Skip to content

Commit

Permalink
[GameStudio] Improve "Add component" button usability. (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfkeenan authored and xen2 committed Apr 13, 2019
1 parent fdad9d8 commit a569a20
Show file tree
Hide file tree
Showing 38 changed files with 717 additions and 69 deletions.
17 changes: 17 additions & 0 deletions sources/core/Xenko.Core/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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()
{
Expand All @@ -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))
{
Expand All @@ -71,6 +87,7 @@ private void Cancel()
{
ClassName = null;
Namespace = null;
ScriptTemplate = null;
Result = Xenko.Core.Presentation.Services.DialogResult.Cancel;
Close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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)
Expand Down
Loading

0 comments on commit a569a20

Please sign in to comment.