Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Tab Menu Customization #13763

Merged
29 commits merged into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7e922ab
Adding a basic NewTabMenuEntry and some scaffolding
FWest98 Aug 16, 2022
88a336b
Add SeparatorEntry but it breaks the build
FWest98 Aug 16, 2022
cec114e
Implemented FolderEntry and SeparatorEntry
FWest98 Aug 16, 2022
294c13a
Add Profile and RemainingProfiles entry definitions
FWest98 Aug 16, 2022
6676785
Full implementation of the feature
FWest98 Aug 16, 2022
ea62c28
Add ProfileCollection abstraction
FWest98 Aug 17, 2022
1d5f6d3
Code cleanup and documentation
FWest98 Aug 17, 2022
40f2a3e
Update schema
FWest98 Aug 17, 2022
b140c75
Code style fixes
FWest98 Aug 17, 2022
fc18f1f
Add words
FWest98 Aug 17, 2022
2e6f1ce
Fixing spelling
FWest98 Sep 10, 2022
bc628f6
Remove ProfilesSourceEntry for now
FWest98 Sep 10, 2022
7b7dc4f
Copyright headers and small code improvements
FWest98 Sep 10, 2022
46a09a4
Remove redundant override
FWest98 Sep 10, 2022
14f6ece
Merge branch 'main' into fw/profiles_dropdown
FWest98 Sep 10, 2022
af0515a
Review comment
FWest98 Sep 10, 2022
5425298
Merge branch 'main' into fw/profiles_dropdown
carlos-zamora Oct 7, 2022
6bceb8c
Apply suggestions from code review
FWest98 Oct 7, 2022
e5d81a1
Merge branch 'main' into fw/profiles_dropdown
FWest98 Nov 20, 2022
6239a7f
Fix merging mistake
FWest98 Nov 21, 2022
afc2f0b
Update resources file with feedback; reintroduce VS spaces
FWest98 Nov 22, 2022
77304b7
Bug bash, review feedback
FWest98 Nov 22, 2022
b7f915f
Implement auto-inlining and emtpy-handling for folders
FWest98 Nov 24, 2022
547b500
Fix code formatting
FWest98 Nov 24, 2022
7ee3602
Add support for matchProfiles entry
FWest98 Nov 24, 2022
86fb995
Update JSON Schema
FWest98 Nov 24, 2022
2473709
Incorporate review feedback
FWest98 Dec 1, 2022
ac703df
Merge remote-tracking branch 'origin/main' into fw/profiles_dropdown
zadjii-msft Dec 6, 2022
ec176b1
Adjust matchProfiles semantics to AND
FWest98 Dec 6, 2022
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
53 changes: 40 additions & 13 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,8 @@
"profile",
"folder",
"separator",
"remainingProfiles"
"remainingProfiles",
"matchProfiles"
]
},
"NewTabMenuEntry": {
Expand All @@ -560,6 +561,12 @@
],
"type": "object"
},
"FolderEntryInlining": {
"enum": [
"never",
"auto"
]
},
"FolderEntry": {
"description": "A folder entry in the new tab dropdown",
"allOf": [
Expand Down Expand Up @@ -587,6 +594,16 @@
"items": {
"$ref": "#/$defs/NewTabMenuEntry"
}
},
"inline": {
"description": "When set to auto and the folder only has a single entry, the entry will show directly and no folder will be rendered",
"default": "never",
"$ref": "#/$defs/FolderEntryInlining"
},
"allowEmpty": {
"description": "Whether to render a folder without entries, or to hide it",
"default": "false",
"type": "boolean"
}
}
}
Expand Down Expand Up @@ -629,8 +646,8 @@
}
]
},
"ProfilesSourceEntry": {
"description": "A set of profiles all generated by the given source, to show in the new tab dropdown",
"RemainingProfilesEntry": {
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
Expand All @@ -639,19 +656,14 @@
"properties": {
"type": {
"type": "string",
"const": "source"
},
"source": {
"type": "string",
"default": "",
"description": "The source of the profiles to show in this entry"
"const": "remainingProfiles"
}
}
}
]
},
"RemainingProfilesEntry": {
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
"MatchProfilesEntry": {
"description": "A set of profiles all matching the given name, source, or command line, to show in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
Expand All @@ -660,7 +672,22 @@
"properties": {
"type": {
"type": "string",
"const": "remainingProfiles"
"const": "matchProfiles"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the profiles to match"
},
"source": {
"type": "string",
"default": "",
"description": "The source of the profiles to match"
},
"commandline": {
"type": "string",
"default": "",
"description": "The command line of the profiles to match"
}
}
}
Expand Down Expand Up @@ -1688,7 +1715,7 @@
"$ref": "#/$defs/ProfileEntry"
},
{
"$ref": "#/$defs/ProfilesSourceEntry"
"$ref": "#/$defs/MatchProfilesEntry"
},
{
"$ref": "#/$defs/RemainingProfilesEntry"
Expand Down
77 changes: 40 additions & 37 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

<!--
Microsoft ResX Schema
Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -232,19 +232,19 @@
<value>Warnings were found while parsing your keybindings:</value>
</data>
<data name="TooManyKeysForChord" xml:space="preserve">
<value>&#x2022; Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"","&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"",""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="FailedToParseSubCommands" xml:space="preserve">
<value>&#x2022; Failed to parse all subcommands of nested command.</value>
<value> Failed to parse all subcommands of nested command.</value>
</data>
<data name="MissingRequiredParameter" xml:space="preserve">
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="UnknownTheme" xml:space="preserve">
<value>&#x2022; The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="LegacyGlobalsProperty" xml:space="preserve">
<value>The "globals" property is deprecated - your settings might need updating. </value>
Expand Down Expand Up @@ -753,8 +753,8 @@
<comment>{0} will be replaced with a number.</comment>
</data>
<data name="DuplicateRemainingProfilesEntry" xml:space="preserve">
<value>&#x2022; The "newTabMenu" structure contains more than one entry of type "remainingProfiles". Only the first is considered.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value>The "newTabMenu" field contains more than one entry of type "remainingProfiles". Only the first one will be considered.</value>
<comment>{Locked="newTabMenu"} {Locked="remainingProfiles"}</comment>
</data>
<data name="AboutToolTip" xml:space="preserve">
<value>Open a dialog containing product information</value>
Expand Down Expand Up @@ -792,4 +792,7 @@
<data name="TabCloseToolTip" xml:space="preserve">
<value>Close this tab</value>
</data>
</root>
<data name="NewTabMenuFolderEmpty" xml:space="preserve">
<value>Empty...</value>
</data>
</root>
48 changes: 41 additions & 7 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -959,18 +959,51 @@ namespace winrt::TerminalApp::implementation
case NewTabMenuEntryType::Folder:
{
const auto folderEntry = entry.as<FolderEntry>();
const auto folderEntries = folderEntry.Entries();

// If the folder is empty, we should skip the entry if AllowEmpty is false, or
// when the folder should inline.
// The IsEmpty check includes semantics for nested (empty) folders
if (folderEntries.Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
{
break;
}

// Recursively generate flyout items
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);

// If the folder should auto-inline and there is only one item, do so.
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
{
for (auto const& folderEntryItem : folderEntryItems)
{
items.push_back(folderEntryItem);
}

break;
}

// Otherwise, create a flyout
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
folderItem.Text(folderEntry.Name());

auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
folderItem.Icon(icon);

// Recursively generate flyout items
auto folderEntries = _CreateNewTabFlyoutItems(folderEntry.Entries());
for (auto const& folderEntry : folderEntries)
for (const auto& folderEntryItem : folderEntryItems)
{
folderItem.Items().Append(folderEntry);
folderItem.Items().Append(folderEntryItem);
}

// If the folder is empty, and by now we know we set AllowEmpty to true,
// create a placeholder item here
if (folderEntries.Size() == 0)
{
auto placeholder = WUX::Controls::MenuFlyoutItem{};
FWest98 marked this conversation as resolved.
Show resolved Hide resolved
placeholder.Text(RS_(L"NewTabMenuFolderEmpty"));
placeholder.IsEnabled(false);

folderItem.Items().Append(placeholder);
}

items.push_back(folderItem);
Expand All @@ -980,6 +1013,7 @@ namespace winrt::TerminalApp::implementation
// separately. This collection is stored as a map <int, Profile>, so the correct
// profile index is already known.
case NewTabMenuEntryType::RemainingProfiles:
case NewTabMenuEntryType::MatchProfiles:
{
const auto remainingProfilesEntry = entry.as<ProfileCollectionEntry>();
if (remainingProfilesEntry.Profiles() == nullptr)
FWest98 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -1089,14 +1123,14 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems
IconElement TerminalPage::_CreateNewTabFlyoutIcon(const winrt::hstring& icon)
IconElement TerminalPage::_CreateNewTabFlyoutIcon(const winrt::hstring& iconSource)
{
if (icon.empty())
if (iconSource.empty())
{
return nullptr;
}

auto icon = IconPathConverter::IconWUX(icon);
auto icon = IconPathConverter::IconWUX(iconSource);
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);

return icon;
Expand Down
Loading