Skip to content

Commit

Permalink
Add New Tab Menu Customization to Settings UI (microsoft#18015)
Browse files Browse the repository at this point in the history
## Summary of the Pull Request
Adds customization for the New Tab Menu to the settings UI.

- Settings Model changes:
- The Settings UI generally works by creating a copy of the entire
settings model objects on which we apply the changes to. Turns out, we
completely left the NewTabMenu out of that process. So I went ahead and
implemented it.
   -  `FolderEntry`
- `FolderEntry` exposes `Entries()` (used by the new tab menu to figure
out what to actually render) and `RawEntries()` (the actual JSON data
deserialized into settings model objects). I went ahead and exposed
`RawEntries()` since we'll need to apply changes to it to then
serialize.
- View Model:
- `NewTabMenuViewModel` is the main view model that interacts with the
page. It maintains the current view of items and applies changes to the
settings model.
- `NewTabMenuEntryViewModel` and all of the other `_EntryViewModel`
classes are wrappers for the settings model NTM entries.
- `FolderTreeViewEntry` encapsulates `FolderEntryViewModel`. It allows
us to construct a `TreeView` of just folders.
- View changes and additions:
   - Added FontIconGlyph to the SettingContainer
   - Added a New Tab Menu item to the navigation view
- Adding entries: a stack of SettingContainers is used here. We use the
new `FontIconGlyph` to make this look nice!
- Reordering entries: drag and drop is supported! This might not work in
admin mode though, and we can't drag and drop into folders. Buttons were
added to make this keyboard accessible.
- To move entries into a folder, a button was added which then displays
a TreeView of all folders.
   - Multiple entries can be moved to a folder or deleted at once!
   - Breadcrumbs are used for folders
- When a folder is entered, additional controls are displayed to
customize that folder.
 
## Verification
- ✅ a11y pass
- ✅ keyboard accessible
- scenarios:
   - ✅ add entries (except actions)
   - ✅ changes propagated to settings model (aka "saving works")
   - ✅ reorder entries
   - ✅ move entries to an existing folder
   - ✅ delete multiple entries
   - ✅ delete individual entries
   - ✅ display entries (including actions)

## Follow-ups
- [ ] add support for adding and editing action entries
- [ ] when we discard changes or save, it would be cool if we could stay
on the same page
- [ ] allow customizing the folder entry _before_ adding it (current
workaround is to add it, then edit it)
- [ ] improve UI for setting icon (reuse UI from microsoft#17965)
  • Loading branch information
carlos-zamora authored Dec 3, 2024
1 parent 5c55144 commit 0d846ae
Show file tree
Hide file tree
Showing 37 changed files with 2,545 additions and 249 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spelling/allow/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Powerline
ptys
pwn
pwshw
QOL
qof
qps
quickfix
Expand All @@ -73,6 +74,7 @@ shcha
similaritytolerance
slnt
stakeholders
subpage
sustainability
sxn
TLDR
Expand Down
27 changes: 25 additions & 2 deletions src/cascadia/TerminalSettingsEditor/CommonResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Thickness x:Key="StandardControlMargin">0,24,0,0</Thickness>
<x:Double x:Key="StandardBoxMinWidth">250</x:Double>
<x:Double x:Key="StandardControlMaxWidth">1000</x:Double>
<Thickness x:Key="SettingStackMargin">13,0,13,48</Thickness>

<!--
This is for styling the entire items control used on the
Expand All @@ -80,13 +81,13 @@
<!-- Used to stack a group of settings -->
<Style x:Key="SettingsStackStyle"
TargetType="StackPanel">
<Setter Property="Margin" Value="13,0,13,48" />
<Setter Property="Margin" Value="{StaticResource SettingStackMargin}" />
</Style>

<!-- Used to stack a group of settings inside a pivot -->
<Style x:Key="PivotStackStyle"
TargetType="StackPanel">
<Setter Property="Margin" Value="0,0,13,48" />
<Setter Property="Margin" Value="{StaticResource SettingStackMargin}" />
</Style>

<!-- Combo Box -->
Expand Down Expand Up @@ -255,6 +256,17 @@
</Setter>
</Style>

<Style x:Key="ExtraSmallButtonStyle"
BasedOn="{StaticResource BrowseButtonStyle}"
TargetType="Button">
<Setter Property="Height" Value="25" />
<Setter Property="Width" Value="25" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="0" />
</Style>

<Style x:Key="SmallButtonStyle"
BasedOn="{StaticResource BrowseButtonStyle}"
TargetType="Button">
Expand Down Expand Up @@ -288,6 +300,17 @@
<Setter Property="Padding" Value="5" />
</Style>

<Style x:Key="DeleteExtraSmallButtonStyle"
BasedOn="{StaticResource DeleteButtonStyle}"
TargetType="Button">
<Setter Property="Height" Value="25" />
<Setter Property="Width" Value="25" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="0" />
</Style>

<Style x:Key="IconButtonTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Margin" Value="10,0,0,0" />
Expand Down
91 changes: 90 additions & 1 deletion src/cascadia/TerminalSettingsEditor/MainPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "AddProfile.h"
#include "InteractionViewModel.h"
#include "LaunchViewModel.h"
#include "NewTabMenuViewModel.h"
#include "..\types\inc\utils.hpp"
#include <..\WinRTUtils\inc\Utils.h>

Expand All @@ -42,6 +43,7 @@ static const std::wstring_view interactionTag{ L"Interaction_Nav" };
static const std::wstring_view renderingTag{ L"Rendering_Nav" };
static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" };
static const std::wstring_view actionsTag{ L"Actions_Nav" };
static const std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" };
static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" };
static const std::wstring_view addProfileTag{ L"AddProfile" };
static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" };
Expand All @@ -61,6 +63,28 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
InitializeComponent();
_UpdateBackgroundForMica();

_newTabMenuPageVM = winrt::make<NewTabMenuViewModel>(_settingsClone);
_ntmViewModelChangedRevoker = _newTabMenuPageVM.PropertyChanged(winrt::auto_revoke, [this](auto&&, const PropertyChangedEventArgs& args) {
const auto settingName{ args.PropertyName() };
if (settingName == L"CurrentFolder")
{
if (const auto& currentFolder = _newTabMenuPageVM.CurrentFolder())
{
const auto crumb = winrt::make<Breadcrumb>(box_value(currentFolder), currentFolder.Name(), BreadcrumbSubPage::NewTabMenu_Folder);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
}
else
{
// If we don't have a current folder, we're at the root of the NTM
_breadcrumbs.Clear();
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
}
});

_colorSchemesPageVM = winrt::make<ColorSchemesPageViewModel>(_settingsClone);
_colorSchemesPageViewModelChangedRevoker = _colorSchemesPageVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
const auto settingName{ args.PropertyName() };
Expand Down Expand Up @@ -136,12 +160,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_InitializeProfilesList();
// Update the Nav State with the new version of the settings
_colorSchemesPageVM.UpdateSettings(_settingsClone);
_newTabMenuPageVM.UpdateSettings(_settingsClone);

// We'll update the profile in the _profilesNavState whenever we actually navigate to one

// now that the menuItems are repopulated,
// refresh the current page using the breadcrumb data we collected before the refresh
if (const auto& crumb{ lastBreadcrumb.try_as<Breadcrumb>() })
if (const auto& crumb{ lastBreadcrumb.try_as<Breadcrumb>() }; crumb && crumb->Tag())
{
for (const auto& item : _menuItemSource)
{
Expand All @@ -161,6 +186,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return;
}
}
else if (const auto& breadcrumbFolderEntry{ crumb->Tag().try_as<Editor::FolderEntryViewModel>() })
{
if (stringTag == newTabMenuTag)
{
// navigate to the NewTabMenu page,
// _Navigate() will handle trying to find the right subpage
SettingsNav().SelectedItem(item);
_Navigate(breadcrumbFolderEntry, BreadcrumbSubPage::NewTabMenu_Folder);
return;
}
}
}
else if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
{
Expand Down Expand Up @@ -393,6 +429,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
else if (clickedItemTag == newTabMenuTag)
{
if (_newTabMenuPageVM.CurrentFolder())
{
// Setting CurrentFolder triggers the PropertyChanged event,
// which will navigate to the correct page and update the breadcrumbs appropriately
_newTabMenuPageVM.CurrentFolder(nullptr);
}
else
{
// Navigate to the NewTabMenu page
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
}
else if (clickedItemTag == globalProfileTag)
{
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone) };
Expand Down Expand Up @@ -490,6 +542,39 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}

void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage)
{
_PreNavigateHelper();

contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);

if (subPage == BreadcrumbSubPage::None)
{
_newTabMenuPageVM.CurrentFolder(nullptr);
}
else if (const auto& folderEntryVM = ntmEntryVM.try_as<Editor::FolderEntryViewModel>(); subPage == BreadcrumbSubPage::NewTabMenu_Folder && folderEntryVM)
{
// The given ntmEntryVM doesn't exist anymore since the whole tree had to be recreated.
// Instead, let's look for a match by name and navigate to it.
if (const auto& folderPath = _newTabMenuPageVM.FindFolderPathByName(folderEntryVM.Name()); folderPath.Size() > 0)
{
for (const auto& step : folderPath)
{
// Take advantage of the PropertyChanged event to navigate
// to the correct folder and build the breadcrumbs as we go
_newTabMenuPageVM.CurrentFolder(step);
}
}
else
{
// If we couldn't find a reasonable match, just go back to the root
_newTabMenuPageVM.CurrentFolder(nullptr);
}
}
}

void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/)
{
const auto window = CoreWindow::GetForCurrentThread();
Expand Down Expand Up @@ -532,6 +617,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_Navigate(*profileViewModel, subPage);
}
else if (const auto ntmEntryViewModel = tag.try_as<NewTabMenuEntryViewModel>())
{
_Navigate(*ntmEntryViewModel, subPage);
}
else
{
_Navigate(tag.as<hstring>(), subPage);
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalSettingsEditor/MainPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _PreNavigateHelper();
void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage);
void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage);
void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage);

void _UpdateBackgroundForMica();
void _MoveXamlParsedNavItemsIntoItemSource();

winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr };

Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker;
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalSettingsEditor/MainPage.idl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace Microsoft.Terminal.Settings.Editor
Profile_Appearance,
Profile_Terminal,
Profile_Advanced,
ColorSchemes_Edit
ColorSchemes_Edit,
NewTabMenu_Folder
};

runtimeclass Breadcrumb : Windows.Foundation.IStringable
Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/TerminalSettingsEditor/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>

<muxc:NavigationViewItem x:Uid="Nav_NewTabMenu"
Tag="NewTabMenu_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE71d;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>

<muxc:NavigationViewItemHeader x:Uid="Nav_Profiles" />

<muxc:NavigationViewItem x:Name="BaseLayerMenuItem"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
<ClInclude Include="Launch.h">
<DependentUpon>Launch.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="NewTabMenu.h">
<DependentUpon>NewTabMenu.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="pch.h" />
<ClInclude Include="MainPage.h">
<DependentUpon>MainPage.xaml</DependentUpon>
Expand Down Expand Up @@ -109,6 +112,10 @@
<DependentUpon>LaunchViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="NewTabMenuViewModel.h">
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Profiles_Base.h">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -174,6 +181,9 @@
<Page Include="Launch.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="NewTabMenu.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="MainPage.xaml">
<SubType>Designer</SubType>
</Page>
Expand Down Expand Up @@ -233,6 +243,9 @@
<ClCompile Include="Launch.cpp">
<DependentUpon>Launch.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="NewTabMenu.cpp">
<DependentUpon>NewTabMenu.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down Expand Up @@ -272,6 +285,10 @@
<DependentUpon>LaunchViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="NewTabMenuViewModel.cpp">
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="Profiles_Base.cpp">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -338,6 +355,10 @@
<DependentUpon>Launch.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="NewTabMenu.idl">
<DependentUpon>NewTabMenu.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="Interaction.idl">
<DependentUpon>Interaction.xaml</DependentUpon>
<SubType>Code</SubType>
Expand All @@ -361,6 +382,7 @@
<Midl Include="InteractionViewModel.idl" />
<Midl Include="GlobalAppearanceViewModel.idl" />
<Midl Include="LaunchViewModel.idl" />
<Midl Include="NewTabMenuViewModel.idl" />
<Midl Include="Profiles_Base.idl">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Midl Include="LaunchViewModel.idl" />
<Midl Include="EnumEntry.idl" />
<Midl Include="SettingContainer.idl" />
<Midl Include="NewTabMenuViewModel.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand All @@ -49,5 +50,6 @@
<Page Include="SettingContainerStyle.xaml" />
<Page Include="AddProfile.xaml" />
<Page Include="KeyChordListener.xaml" />
<Page Include="NewTabMenu.xaml" />
</ItemGroup>
</Project>
Loading

0 comments on commit 0d846ae

Please sign in to comment.