コンテキスト メニューに選択肢を追加する

Stingray エディタのいくつかの場所では、何らかの項目を右クリックすることにより、選択した項目に関連するアクションを提供する「コンテキスト メニュー」を開くことができます。たとえば、Asset Browser でアセットを右クリックしたり、Level Viewport または Explorer でユニットを右クリックすると、このアセットまたはレベル オブジェクトに適用されるアクションのメニューが表示されます。

プラグインで contextual_actions 拡張機能を使用することにより、これらのコンテキスト メニューで提供される既定のオプション セットに新しい選択肢追加することができます。

新しく設定するコンテキスト メニュー項目ごとに、実行する必要がある 1 つまたは複数のアクション、および現在のコンテキストに基づいてエディタがこの項目を表示するかどうかを判別する 1 つまたは複数の「述語」をエディタに対して指定します。述語を使うことにより、たとえば、選択した項目がユニットまたはライトの場合にのみ、エディタにメニュー項目を表示させることができます。

環境設定

すべてのコンテキスト アクション拡張機能で、次の環境設定パラメータを使用できます。

extensions = {
    contextual_actions = [
        {
            label = "Open In Sublime"
            type = "asset"

            when = {
                ...
            }

            when_multi_select = {
                ...
            }

            do = [
                ...
            ]
        }
    ]
}

label

コンテキスト メニューでこのアクションに対して表示する文字列を定義します。この値は、アクションの一意の名前としても使用されます。必須

type

このアクションを適用できる項目の種類を定義します。asset または level_object を指定することができます。アクション タイプはコンテキスト アクションに対するグローバルな述語として機能します。たとえば、Asset Browser でコンテキスト メニューを入力する必要がある場合は、最初にタイプが asset のすべてのコンテキスト アクションを収集し、その次にカスタムの述語をチェックします。Explorer パネルでも同様ですが、この場合は level_object アクションのみを検索します。また、ユーザ独自のタイプを定義することもできます。必須

when

エディタがコンテキスト メニューにこのアクションを表示するかどうかを決定する述語、または述語の配列です。設定できるさまざまな種類の述語の例については、以下を参照してください。使用できる述語の種類は、コンテキスト アクションに設定された type によって異なることがあります。たとえば、extension 述語を使用できるのは asset タイプのみです。オプション

when_multi_select

when と同様に述語(または述語の配列)を指定しますが、この述語は、エディタが右クリック メニューを開いた時点でユーザが「複数」のアセットまたはレベル項目を選択していた場合のみチェックされます。オプション

do

ユーザがコンテキスト メニューでこの項目を選択したときにプラグインが実行する必要があるアクション、またはアクションの配列です。エディタはこれらの各アクションに、操作が必要なコンテキストを渡します。アセットの場合は、プロジェクト内のアセットのパスです(content/models/character.unit など)。レベル オブジェクトの場合は、editor_slave Lua コードがこのレベル内のオブジェクトを追跡するために使用するレベル オブジェクトの記述子です。アクションの詳細については、「アクションを登録する」を参照してください。必須

単一アセットの述部

1 つのアセットに作用するコンテキスト アクションの場合は、when パラメータに 2 つの方法で述語を設定できます。アセットの extension に基づく方法と、predicate アクションを使用する方法です。

アセットの拡張子

選択したアセットの拡張子に基づいて述語がコンテキスト アクションを許可するように設定できます。extension パラメータを使用して、文字列または文字列の配列を指定することができます。選択したアセットのファイル拡張子がこれらの文字列のいずれかと一致した場合、エディタはコンテキスト アクションを表示します。

たとえば、ユーザが Asset Browser.png または .jpg ファイルを右クリックしたときに、エディタにコンテキスト アクションを表示できるようになります。

{
    label = "Open in Paint"
    type = "asset"

    when = {
        // Matches both types of image file
        extension = [
            "png"
            "jpg"
        ]
    }

    do = [
        {
            type = "process"
            path = "\"C:\\Program Files\\paint.net\\PaintDotNet.exe\" \"$project\\$1\""
        }
    ]
}

また、ワイルドカード文字 * を使用して、あらゆるファイル拡張子を許可することもできます。

{
    type = "asset"
    label = "Open In Sublime"

    when = {
        // Matches any asset type
        extension = "*"
    }

    do = [
        {
            type = "process"
            path = "\"C:\\Program Files\\Sublime Text 3\\sublime_text.exe\" \"$project\\$1\""
        }
    ]
}

アクションの述語

さらに詳細なカスタム処理を行って、ファイル拡張子を確認するだけでなく、コンテキスト アクションを許可するかどうかを決定する必要がある場合は、predicate キーを使用して、述語に対応するアクションを設定することができます。

たとえば、次の環境設定では、module-actions JavaScript モジュール内で定義されている isReadOnly() 関数が実行されます。このアクションが true を返した場合は、コンテキスト メニューにメニュー項目が表示されます。

// In tests-actions.stingray_plugin
{
    label = "Checkout"
    type = "asset"

    when = {
        extension = "*"
        predicate = {
            type = "js"
            module = "module-actions"
            function_name = "isReadOnly"
        }
    }

    do = [
        {
            type = "js"
            module = "module-actions"
            function_name = "checkout"
        }
    ]
}
// In module-actions.js

define(['common/file-system-utils', 'services/file-system-service', 'services/project-service'], function (fileUtils, fileSystemService, assetService, projectService) {

    return {
        // The first parameter passed to an asset predicate is the path to the asset within the project (effectively the
        // asset resource name and its file extension). For example, content/models/character.unit
        isReadOnly: function (assetRelativePath) {

            // The API for invoking predicate actions is Promise based (thus asynchronous). The final Promise must return something
            // "truthy" in order for the contextual action to appear in the contextual menu.
            return projectService.relativePathToAbsolute(assetRelativePath).then(function (absPath) {
                return fileSystemService.isReadOnly(absPath);
            });
        },
    }
});

マルチアセットの述語

複数のアセットが選択されている場合にコンテキスト アクションを表示する必要がある場合は、1 つのアセットを処理する場合と同様に when 述語を設定します。ただし、when_multi_select 述語を設定して、コンテキスト アクションで複数のアセットを許可するかどうかを判別する必要もあります。

また、ユーザがコンテキスト アクションをトリガすると、選択されたすべてのアセットは単一文字列としてではなく、配列としてアクションに渡されます。1 つを選択した場合と複数を選択した場合の両方の処理に同じ関数を使用する場合は、次の例のように、この 2 つの場合を適切に処理する必要があります。

// Example in tests-actions.stingray_plugin
{
    type = "asset"
    label = "Duplicate"

    // Note that this action works both for single asset and multi selection of assets
    when = {
        extension = "*"
    }

    // when_multi_select is a predicate.
    when_multi_select = {
        // This is a nice trick when_multi_select should always be true: invoke an in-place script that returns true.
        type = "js"
        script = "true"
    }

    do = [
        {
            type = "js"
            module = "module-actions"
            function_name = "duplicateAssets"
        }
    ]
}
// In module-actions.js
define(['common/file-system-utils', 'services/file-system-service', 'services/project-service'], function (fileUtils, fileSystemService, assetService, projectService) {

    return {
        // Since this action can be invoked both for single and multi selection, we need to check whether its parameter is an array or not.
        duplicateAssets: function (assets) {
            if (!_.isArray(assets)) {
                assets = [assets];
            }

            // Note that each elements in the assets array is a relative asset path.
            _.each(assets, function (assetRelativePath) {
                // Do something to duplicate an asset
            });
        },
    }
});

単一オブジェクトの述語

レベル オブジェクトの述語は、アセットの場合と同様に設定します。ただし、ファイル拡張子を使用する代わりに、category を使用して、アクションがサポートするレベル オブジェクトのカテゴリを 1 つまたは複数定義することができます。また、独自の処理を行うように predicate を設定することもできます。

たとえば、この述語では「unit」カテゴリ内のレベル オブジェクトのみが許可されます。

// From asset-browser.stingray_plugin
contextual_actions = [
    {
        type = "level_object"
        label = "Find in Asset Browser"

        // Context specific for asset types
        when = {
                category = "unit"
        }

        do = [
            {
                type = "js"
                module = "asset-browser-actions"
                function_name = "findLevelUnitInAssetBrowser"
            }
        ]
    }
]

使用できるカテゴリ値のリストは、エディタに表示される .object_filter リソースに基づいて動的に作成されます。既定では、これらには core/editor_slave/resources/filters 内のすべてのフィルタが含まれます。

ワイルドカード文字 * を使用して、すべてのカテゴリと一致させることもできます。

拡張子と同様に、複数のカテゴリを配列として指定することもできます。

{
    type = "level_object"
    label = "Reset position"

    when = {
        category = ["unit", "light"]
    }

    do = [
        {
            type = "js"
            module = "asset-browser-actions"
            function_name = "setPosToZero"
        }
    ]
}

また、アセットの場合と同じ方法で、カスタムの述語を記述することもできます。

// In test-actions.stingray_plugin
{
    type = "level_object"
    label = "Close door"

    // Context specific for asset types
    when = {
        category = "unit"
        predicate = {
            type = "js"
            module = "module-actions"
            function_name = "isDoor"
        }
    }

    do = [
        {
            type = "js"
            module = "module-actions"
            function_name = "closeDoor"
        }
    ]
}
// In module-actions.js
isDoor: function (levelObjectTreeNode) {
    // If the unit resource that corresponds to the level object is a door, allow the user to close it.
    return levelObjectTreeNode.dataContext.Type &&
        levelObjectTreeNode.dataContext.Type.startsWith('model/content/door_');
}

マルチオブジェクトの述語

複数のレベル オブジェクトが選択されている場合にコンテキスト アクションを表示する必要がある場合は、1 つのオブジェクトを処理する場合と同様に when 述語を設定します。ただし、when_multi_select 述語を設定して、コンテキスト アクションで複数のオブジェクトを許可するかどうかを判別する必要もあります。

また、ユーザがコンテキスト アクションをトリガすると、選択されたすべてのオブジェクトは単一文字列としてではなく、配列としてアクションに渡されます。1 つを選択した場合と複数を選択した場合の両方の処理に同じ関数を使用する場合は、この 2 つの場合を適切に処理する必要があります。

{
    type = "level_object"
    label = "Duplicate Objects"

    // Works both for single level object selection and for multi selection.
    when = {
        category = "*"
    }

    when_multi_select = {
        type = "js"
        script = "true"
    }

    do = [
        {
            type = "js"
            module = "module-actions"
            function_name = "duplicateLevelObjects"
        }
    ]
}
// from module-actions.js
duplicateLevelObjects: function (levelObjects) {
    if (!_.isArray(levelObjects)) {
        levelObjects = [levelObjects];
    }

    _.each(levelObjects, function (levelObject) {
        // dataContext corresponds to the level object Backend Remote Object. Id is the Level Obejct unique id in the level.
        levelEditingService.duplicate(levelObject.dataContext.Id);
    });
}