Allow actions in the new tab dropdown (#17281)

Allows the user to define entries in the new tab menu that execute
actions, based on their action Id

Closes #3759
Closes #9362
This commit is contained in:
PankajBhojwani 2024-06-06 15:17:18 -07:00 committed by GitHub
parent d6b6aacb4f
commit aeed0782bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 178 additions and 4 deletions

View File

@ -629,7 +629,8 @@
"folder",
"separator",
"remainingProfiles",
"matchProfiles"
"matchProfiles",
"action"
]
},
"NewTabMenuEntry": {
@ -781,6 +782,28 @@
}
]
},
"ActionEntry": {
"description": "An action in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "action"
},
"id": {
"type": "string",
"default": "",
"description": "The ID of the action to show in this entry"
}
}
}
]
},
"SwitchToAdjacentTabArgs": {
"oneOf": [
{
@ -2054,6 +2077,9 @@
},
{
"$ref": "#/$defs/RemainingProfilesEntry"
},
{
"$ref": "#/$defs/ActionEntry"
}
]
}

View File

@ -1004,6 +1004,18 @@ namespace winrt::TerminalApp::implementation
items.push_back(profileItem);
break;
}
case NewTabMenuEntryType::Action:
{
const auto actionEntry = entry.as<ActionEntry>();
const auto actionId = actionEntry.ActionId();
if (_settings.ActionMap().GetActionByID(actionId))
{
auto actionItem = _CreateNewTabFlyoutAction(actionId);
items.push_back(actionItem);
}
break;
}
}
}
@ -1094,6 +1106,41 @@ namespace winrt::TerminalApp::implementation
return profileMenuItem;
}
// Method Description:
// - This method creates a flyout menu item for a given action
// It makes sure to set the correct icon, keybinding, and click-action.
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutAction(const winrt::hstring& actionId)
{
auto actionMenuItem = WUX::Controls::MenuFlyoutItem{};
const auto action{ _settings.ActionMap().GetActionByID(actionId) };
const auto actionKeyChord{ _settings.ActionMap().GetKeyBindingForAction(actionId) };
if (actionKeyChord)
{
_SetAcceleratorForMenuItem(actionMenuItem, actionKeyChord);
}
actionMenuItem.Text(action.Name());
// If there's an icon set for this action, set it as the icon for
// this flyout item
const auto& iconPath = action.IconPath();
if (!iconPath.empty())
{
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
actionMenuItem.Icon(icon);
}
actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_actionDispatch->DoAction(action.ActionAndArgs());
}
});
return actionMenuItem;
}
// Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems

View File

@ -300,6 +300,7 @@ namespace winrt::TerminalApp::implementation
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId);
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs);

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ActionEntry.h"
#include "JsonUtils.h"
#include "ActionEntry.g.cpp"
using namespace Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
static constexpr std::string_view ActionIdKey{ "id" };
ActionEntry::ActionEntry() noexcept :
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
{
}
Json::Value ActionEntry::ToJson() const
{
auto json = NewTabMenuEntry::ToJson();
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
return json;
}
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
{
auto entry = winrt::make_self<ActionEntry>();
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
return entry;
}

View File

@ -0,0 +1,37 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- ActionEntry.h
Abstract:
- An action entry in the "new tab" dropdown menu
Author(s):
- Pankaj Bhojwani - May 2024
--*/
#pragma once
#include "NewTabMenuEntry.h"
#include "ActionEntry.g.h"
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry>
{
public:
ActionEntry() noexcept;
Json::Value ToJson() const override;
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
WINRT_PROPERTY(winrt::hstring, ActionId);
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ActionEntry);
}

View File

@ -596,6 +596,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _GetActionByKeyChordInternal(keys).value_or(nullptr);
}
Model::Command ActionMap::GetActionByID(const winrt::hstring& cmdID) const
{
return _GetActionByID(cmdID);
}
// Method Description:
// - Retrieves the assigned command ID with the given key chord.
// - Can return nullopt to differentiate explicit unbinding vs lack of binding.

View File

@ -60,6 +60,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// queries
Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const;
Model::Command GetActionByID(const winrt::hstring& cmdID) const;
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID);

View File

@ -11,7 +11,7 @@ namespace Microsoft.Terminal.Settings.Model
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByID(String cmdID);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };

View File

@ -28,6 +28,9 @@
<ClInclude Include="SeparatorEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ActionEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
<ClInclude Include="FolderEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
@ -185,6 +188,9 @@
<ClCompile Include="SeparatorEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ActionEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>
<ClCompile Include="FolderEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>

View File

@ -8,6 +8,7 @@
#include "SeparatorEntry.h"
#include "FolderEntry.h"
#include "ProfileEntry.h"
#include "ActionEntry.h"
#include "RemainingProfilesEntry.h"
#include "MatchProfilesEntry.h"
@ -52,6 +53,8 @@ winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& jso
return RemainingProfilesEntry::FromJson(json);
case NewTabMenuEntryType::MatchProfiles:
return MatchProfilesEntry::FromJson(json);
case NewTabMenuEntryType::Action:
return ActionEntry::FromJson(json);
default:
return nullptr;
}

View File

@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model
Separator,
Folder,
RemainingProfiles,
MatchProfiles
MatchProfiles,
Action
};
[default_interface] unsealed runtimeclass NewTabMenuEntry
@ -34,6 +35,13 @@ namespace Microsoft.Terminal.Settings.Model
Int32 ProfileIndex;
}
[default_interface] runtimeclass ActionEntry : NewTabMenuEntry
{
ActionEntry();
String ActionId;
}
enum FolderEntryInlining
{
Never = 0,

View File

@ -678,8 +678,9 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
// Possible NewTabMenuEntryType values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType)
{
JSON_MAPPINGS(5) = {
JSON_MAPPINGS(6) = {
pair_type{ "profile", ValueType::Profile },
pair_type{ "action", ValueType::Action },
pair_type{ "separator", ValueType::Separator },
pair_type{ "folder", ValueType::Folder },
pair_type{ "remainingProfiles", ValueType::RemainingProfiles },

View File

@ -38,6 +38,9 @@
<ClInclude Include="../Profile.h" />
<ClInclude Include="../TerminalWarnings.h" />
<ClInclude Include="../NewTabMenuEntry.h" />
<ClInclude Include="../ActionEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude>
<ClInclude Include="../SeparatorEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude>