Compare commits

...

59 Commits

Author SHA1 Message Date
Carlos Zamora
91dbd65712 PRE-MERGE #19817 Implement the Kitty Keyboard Protocol 2026-01-29 15:32:28 -08:00
Leonard Hecker
5d0e8c238c Spel 2026-01-30 00:01:10 +01:00
Leonard Hecker
07792774f6 Implement the Kitty Keyboard Protocol 2026-01-29 23:58:10 +01:00
Carlos Zamora
248ffa139e PRE-MERGE #19647 Fix tab row acrylic material in unfocused windows 2026-01-29 14:45:26 -08:00
Carlos Zamora
a214c37542 PRE-MERGE #19591 Add IconPicker to New Tab Menu folders in SUI 2026-01-29 14:45:08 -08:00
Carlos Zamora
09bcdc0566 Merge branch 'main' into dev/cazamor/sui/search 2026-01-29 14:29:32 -08:00
Carlos Zamora
cee7393e1e address Pankaj's feedback 2026-01-28 11:33:48 -08:00
Carlos Zamora
26644183f2 Polish Extensions results 2026-01-28 10:50:22 -08:00
Carlos Zamora
8f7a26d05c Add support for searching actions 2026-01-28 09:53:41 -08:00
Carlos Zamora
7a05a7c603 create NavConstants.h to deduplicate code; spellcheck 2026-01-27 18:36:17 -08:00
Carlos Zamora
c370efbdad remove some dead code 2026-01-27 18:11:46 -08:00
Carlos Zamora
543c67771e remove help text from index 2026-01-27 17:48:39 -08:00
Carlos Zamora
5471c4c400 tune fzf scoring 2026-01-27 17:36:57 -08:00
Carlos Zamora
5d188ce593 Move search logic to SearchIndex; add fzf 2026-01-27 11:35:26 -08:00
Carlos Zamora
469a6d73be Merge branch 'main' into dev/cazamor/sui/ntm-icon-picker 2026-01-20 10:44:24 -08:00
Carlos Zamora
82a986af94 Merge branch 'main' into dev/cazamor/sui/search 2026-01-20 10:43:59 -08:00
Carlos Zamora
62a6b5eb2f address feedback from Pankaj's review 2026-01-16 17:15:17 -08:00
Carlos Zamora
6cc6fe1714 fix memory leak 2026-01-16 14:49:57 -08:00
Carlos Zamora
14e380a9b2 minor code health in _UpdateSearchIndex() 2026-01-15 15:51:35 -08:00
Carlos Zamora
9cbaa980d8 exclude Profiles_Base_Orphaned from index 2026-01-14 19:09:08 -08:00
Carlos Zamora
27d4a0b575 prevent multiple searches if we're not gonna use them 2026-01-14 18:27:13 -08:00
Carlos Zamora
e14dfec7b7 fix duplicate profile results when reloading 2026-01-14 18:04:57 -08:00
Carlos Zamora
672945c3bf Add secondary label to search results 2026-01-14 17:35:09 -08:00
Carlos Zamora
d008e80d02 fix search results + profile.defaults bugs 2026-01-14 15:27:37 -08:00
Carlos Zamora
3252b7ddf0 Merge branch 'main' into dev/cazamor/sui/search 2026-01-12 16:52:21 -08:00
Carlos Zamora
fe4069dd13 Merge branch 'main' into fix-tab-acrylic-unfocused 2026-01-12 16:16:55 -08:00
Carlos Zamora
3abee35102 Remove shouldUseAcrylic 2026-01-12 13:57:43 -08:00
Carlos Zamora
acb19efea7 Remove unused variable 2026-01-12 13:18:23 -08:00
Carlos Zamora
22ba243185 Merge branch 'main' into dev/cazamor/sui/search 2025-12-16 16:16:44 -08:00
AbhishekGiri04
018fade640 Fix tab row acrylic material in unfocused windows (#19544)
This commit fixes the issue where the tab row loses its acrylic material
when the window loses focus, even when 'Allow acrylic material in unfocused
windows' setting is enabled.

The fix modifies the _updateThemeColors() function in TerminalPage.cpp to
check both the UseAcrylicInTabRow setting AND the window focus state along
with the EnableUnfocusedAcrylic setting. Now the tab row will maintain its
acrylic effect when:
1. UseAcrylicInTabRow is enabled, AND
2. Either the window is focused OR EnableUnfocusedAcrylic is enabled

This ensures that the 'Allow acrylic material in unfocused windows' setting
is properly respected for the tab row, not just the terminal background.

Fixes #19544
2025-12-15 20:14:42 +05:30
Carlos Zamora
1a73aa6367 Merge branch 'main' into dev/cazamor/sui/ntm-icon-picker 2025-12-02 15:45:28 -08:00
Carlos Zamora
e87cc1f346 code format 2025-12-02 15:42:07 -08:00
Carlos Zamora
0228206e02 update resources and variable names 2025-12-02 15:41:19 -08:00
Carlos Zamora
072ab20a4d Add IconPicker to New Tab Menu folders in SUI 2025-11-25 15:50:52 -08:00
Carlos Zamora
6d968b54f3 remove TODO CARLOS 2025-11-24 16:19:22 -08:00
Carlos Zamora
20bdc21c79 clean up more TODOs 2025-11-17 17:42:43 -08:00
Carlos Zamora
a4c69cfc6a clean up some TODOs 2025-11-17 17:35:45 -08:00
Carlos Zamora
e5ea64586d NavigateToXArgs --> NavigateToPageArgs 2025-11-17 16:49:57 -08:00
Carlos Zamora
81f881a579 Fix navigation for profile.appearance settings (global profile too) 2025-11-17 15:41:19 -08:00
Carlos Zamora
45d75e701f registerIndex macro -> lambda 2025-11-17 14:11:56 -08:00
Carlos Zamora
83aa9fd889 profiles: fix BringIntoView (partially) 2025-11-13 10:34:20 -08:00
Carlos Zamora
8c99200e96 color schemes: fix BringIntoView 2025-11-12 15:49:00 -08:00
Carlos Zamora
14bab6cc1a more polish; more bugs found; I guess it's a stalemate 2025-11-07 12:24:33 -08:00
Carlos Zamora
be2b1d30cb fix color schemes; convert APPEND_RUNTIME_OBJECT_RESULTS to lambda 2025-11-06 16:17:32 -08:00
Carlos Zamora
6fbf953fb2 clear search box 2025-11-06 15:32:35 -08:00
Carlos Zamora
cff62cc60e update selected item 2025-11-06 13:57:13 -08:00
Carlos Zamora
82536fd756 bugfix: duplicate profile 2025-11-05 15:26:54 -08:00
Carlos Zamora
bf2e4e19d7 code format and spell check 2025-11-05 15:26:34 -08:00
Carlos Zamora
2706d05491 icons 2025-11-05 14:54:07 -08:00
Carlos Zamora
1dafcef36f FilteredSearchResult::CreateNoResultsItem and CreateRuntimeObjectItem 2025-11-05 13:02:30 -08:00
Carlos Zamora
53ddd92e7f remove SearchMetadata (old attempt) 2025-11-03 17:26:08 -08:00
Carlos Zamora
e84e8d408f add language neutral search 2025-11-03 17:02:03 -08:00
Carlos Zamora
532343f1ce runtime object indexing 2025-11-03 16:27:12 -08:00
Carlos Zamora
2a41f8a57c improve index 2025-11-03 11:48:36 -08:00
Carlos Zamora
e56eb74788 improve indexing perf and include help text 2025-10-29 15:01:31 -07:00
Carlos Zamora
cac844b1e9 minimum viable product 2025-10-28 12:59:31 -07:00
Carlos Zamora
915f085b60 load runtime index for search 2025-10-23 13:32:18 -07:00
Carlos Zamora
1b8c99dff8 Load build time index for search
- Adds referential XAML names to components of interest.
- Adds a script that generates the build time entries to be loaded
- Invokes the script in Editor.vcxproj
2025-10-15 15:44:17 -07:00
Carlos Zamora
e01ff4faf0 Introduce SearchMetadata and navigation by name
- SearchMetadata is unsused, but it is designed to hold information
   so that we can navigate to a setting
- Updated all pages to...
   - have an x:Name on relevant setting containers
   - have a NavigateToXArgs used in the OnNavigatedTo() function
   - update the NavigateToXArgs to include the name of an element
      to scroll down to
   - Add BringIntoViewWhenLoaded() to HasScrollViewer<T> which
      scrolls down to the element with a given name
These components aren't fully hooked up together yet and there's a
few TODO CARLOS's throughout. Main upcoming work:
- indexing
- runtime indexing
- search box UI
- search results UI
based on 079c69b8be/doc/specs/settings-search.md
2025-10-14 11:46:31 -07:00
97 changed files with 3788 additions and 791 deletions

View File

@@ -71,7 +71,9 @@ sustainability
sxn
Tencent
toolset
Uids
UEFI
UIDs
uiatextrange
und
vsdevcmd

View File

@@ -866,6 +866,7 @@ KILLACTIVE
KILLFOCUS
kinda
KIYEOK
KKP
KLF
KLMNO
KOK
@@ -885,6 +886,7 @@ LBUTTONDOWN
LBUTTONUP
lcb
lci
LCMAP
LCONTROL
LCTRL
lcx

View File

@@ -134,7 +134,7 @@
</ClInclude>
<ClInclude Include="FilteredCommand.h" />
<ClInclude Include="Pane.h" />
<ClInclude Include="fzf/fzf.h" />
<ClInclude Include="../fzf/fzf.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ShortcutActionDispatch.h">
<DependentUpon>ShortcutActionDispatch.idl</DependentUpon>
@@ -204,7 +204,7 @@
<ClCompile Include="Tab.cpp">
<DependentUpon>Tab.idl</DependentUpon>
</ClCompile>
<ClCompile Include="fzf/fzf.cpp" />
<ClCompile Include="../fzf/fzf.cpp" />
<ClCompile Include="TaskbarState.cpp">
<DependentUpon>TaskbarState.idl</DependentUpon>
</ClCompile>

View File

@@ -4916,7 +4916,7 @@ namespace winrt::TerminalApp::implementation
theme.TabRow().UnfocusedBackground()) :
ThemeColor{ nullptr } };
if (_settings.GlobalSettings().UseAcrylicInTabRow())
if (_settings.GlobalSettings().UseAcrylicInTabRow() && (_activated || _settings.GlobalSettings().EnableUnfocusedAcrylic()))
{
if (tabRowBg)
{

View File

@@ -121,6 +121,7 @@ namespace Microsoft.Terminal.Core
String WordDelimiters { get; };
Boolean ForceVTInput { get; };
Boolean AllowKittyKeyboardMode { get; };
Boolean AllowVtChecksumReport { get; };
Boolean AllowVtClipboardWrite { get; };
Boolean TrimBlockSelection { get; };

View File

@@ -98,6 +98,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
}
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
_getTerminalInput().ForceDisableKittyKeyboardProtocol(!settings.AllowKittyKeyboardMode());
if (settings.TabColor() == nullptr)
{

View File

@@ -349,6 +349,7 @@ namespace winrt::Microsoft::Terminal::Settings
_ReloadEnvironmentVariables = profile.ReloadEnvironmentVariables();
_RainbowSuggestions = profile.RainbowSuggestions();
_ForceVTInput = profile.ForceVTInput();
_AllowKittyKeyboardMode = profile.AllowKittyKeyboardMode();
_AllowVtChecksumReport = profile.AllowVtChecksumReport();
_AllowVtClipboardWrite = profile.AllowVtClipboardWrite();
_PathTranslationStyle = profile.PathTranslationStyle();

View File

@@ -4,6 +4,8 @@
#include "pch.h"
#include "Actions.h"
#include "Actions.g.cpp"
#include "NavigateToPageArgs.g.h"
#include "LibraryResources.h"
#include "../TerminalSettingsModel/AllShortcutActions.h"
using namespace winrt::Windows::UI::Xaml;
@@ -20,16 +22,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Actions::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::ActionsViewModel>();
_ViewModel.CurrentPage(ActionsSubPage::Base);
auto vmImpl = get_self<ActionsViewModel>(_ViewModel);
vmImpl->MarkAsVisited();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::ActionsViewModel>();
get_self<ActionsViewModel>(_ViewModel)->MarkAsVisited();
_layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
AddNewButton().Focus(FocusState::Programmatic);
});
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -53,13 +53,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
public:
NavigateToCommandArgs(CommandViewModel command, Editor::IHostedInWindow windowRoot) :
_Command(command),
_WindowRoot(windowRoot) {}
_WeakWindowRoot(windowRoot) {}
Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; }
Editor::IHostedInWindow WindowRoot() const noexcept { return _WeakWindowRoot.get(); }
Editor::CommandViewModel Command() const noexcept { return _Command; }
private:
Editor::IHostedInWindow _WindowRoot;
winrt::weak_ref<Editor::IHostedInWindow> _WeakWindowRoot;
Editor::CommandViewModel _Command{ nullptr };
};

View File

@@ -27,6 +27,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AddProfile::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::AddProfilePageNavigationState>();
BringIntoViewWhenLoaded(_State.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -26,8 +26,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct AddProfilePageNavigationState : AddProfilePageNavigationStateT<AddProfilePageNavigationState>
{
public:
AddProfilePageNavigationState(const Model::CascadiaSettings& settings) :
_Settings{ settings } {}
AddProfilePageNavigationState(const Model::CascadiaSettings& settings, const hstring& elementToFocus = {}) :
_Settings{ settings },
_ElementToFocus{ elementToFocus } {}
void RequestAddNew()
{
@@ -42,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
til::event<AddNewArgs> AddNew;
WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr);
WINRT_PROPERTY(hstring, ElementToFocus);
};
struct AddProfile : public HasScrollViewer<AddProfile>, AddProfileT<AddProfile>

View File

@@ -8,6 +8,8 @@ namespace Microsoft.Terminal.Settings.Editor
runtimeclass AddProfilePageNavigationState
{
Microsoft.Terminal.Settings.Model.CascadiaSettings Settings;
String ElementToFocus { get; };
void RequestAddNew();
void RequestDuplicate(Guid profile);
event AddNewArgs AddNew;

View File

@@ -37,7 +37,8 @@
</Button>
</Border>
<StackPanel Margin="{StaticResource StandardControlMargin}">
<local:SettingContainer x:Uid="AddProfile_Duplicate">
<local:SettingContainer x:Name="DuplicateProfile"
x:Uid="AddProfile_Duplicate">
<ComboBox x:Name="Profiles"
AutomationProperties.AccessibilityView="Content"
ItemsSource="{x:Bind State.Settings.AllProfiles, Mode=OneWay}"

View File

@@ -1138,6 +1138,27 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content");
}
// Appearances doesn't implement HasScrollViewer<T> which normally adds this function.
void Appearances::BringIntoViewWhenLoaded(hstring elementToFocus)
{
if (elementToFocus.empty())
{
return;
}
_loadedRevoker = this->Loaded(winrt::auto_revoke, [weakThis{ get_weak() }, elementToFocus](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
if (const auto& controlToFocus{ strongThis->FindName(elementToFocus).try_as<Controls::Control>() })
{
controlToFocus.as<FrameworkElement>().StartBringIntoView();
controlToFocus.Focus(FocusState::Programmatic);
}
strongThis->_loadedRevoker.revoke();
}
});
}
IObservableVector<Editor::Font> Appearances::FilteredFontList()
{
if (!_filteredFonts)

View File

@@ -188,6 +188,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct Appearances : AppearancesT<Appearances>
{
Appearances();
void BringIntoViewWhenLoaded(hstring elementToFocus);
// CursorShape visibility logic
bool IsVintageCursor() const;
@@ -210,6 +211,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool IsCustomFontWeight();
til::property_changed_event PropertyChanged;
winrt::Windows::UI::Xaml::FrameworkElement::Loaded_revoker _loadedRevoker;
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::EnumEntry>, FontWeightList);

View File

@@ -75,7 +75,8 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Color Scheme -->
<!-- This currently only display the Dark color scheme, even if the user has a pair of schemes set. -->
<local:SettingContainer x:Uid="Profile_ColorScheme"
<local:SettingContainer x:Name="ColorScheme"
x:Uid="Profile_ColorScheme"
ClearSettingValue="{x:Bind Appearance.ClearColorScheme}"
CurrentValueAccessibleName="{x:Bind Appearance.CurrentColorScheme.Name, Mode=OneWay}"
HasSettingValue="{x:Bind Appearance.HasDarkColorSchemeName, Mode=OneWay}"
@@ -284,17 +285,20 @@
IsChecked="{x:Bind ShowAllFonts, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>
<local:SettingContainer x:Uid="Profile_MissingFontFaces"
<local:SettingContainer x:Name="MissingFontFaces"
x:Uid="Profile_MissingFontFaces"
HelpText="{x:Bind Appearance.MissingFontFaces, Mode=OneWay}"
Style="{StaticResource SettingContainerErrorStyle}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Appearance.MissingFontFaces), Mode=OneWay}" />
<local:SettingContainer x:Uid="Profile_ProportionalFontFaces"
<local:SettingContainer x:Name="ProportionalFontFaces"
x:Uid="Profile_ProportionalFontFaces"
HelpText="{x:Bind Appearance.ProportionalFontFaces, Mode=OneWay}"
Style="{StaticResource SettingContainerWarningStyle}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Appearance.ProportionalFontFaces), Mode=OneWay}" />
<!-- Font Size -->
<local:SettingContainer x:Uid="Profile_FontSize"
<local:SettingContainer x:Name="FontSize"
x:Uid="Profile_FontSize"
ClearSettingValue="{x:Bind Appearance.ClearFontSize}"
HasSettingValue="{x:Bind Appearance.HasFontSize, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.FontSizeOverrideSource, Mode=OneWay}"
@@ -310,7 +314,8 @@
</local:SettingContainer>
<!-- Line Height -->
<local:SettingContainer x:Uid="Profile_LineHeight"
<local:SettingContainer x:Name="LineHeight"
x:Uid="Profile_LineHeight"
ClearSettingValue="{x:Bind Appearance.ClearLineHeight}"
HasSettingValue="{x:Bind Appearance.HasLineHeight, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.LineHeightOverrideSource, Mode=OneWay}"
@@ -326,7 +331,8 @@
</local:SettingContainer>
<!-- Cell Width -->
<local:SettingContainer x:Uid="Profile_CellWidth"
<local:SettingContainer x:Name="CellWidth"
x:Uid="Profile_CellWidth"
ClearSettingValue="{x:Bind Appearance.ClearCellWidth}"
HasSettingValue="{x:Bind Appearance.HasCellWidth, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.CellWidthOverrideSource, Mode=OneWay}"
@@ -342,7 +348,8 @@
</local:SettingContainer>
<!-- Font Weight -->
<local:SettingContainer x:Uid="Profile_FontWeight"
<local:SettingContainer x:Name="FontWeight"
x:Uid="Profile_FontWeight"
ClearSettingValue="{x:Bind Appearance.ClearFontWeight}"
HasSettingValue="{x:Bind Appearance.HasFontWeight, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.FontWeightOverrideSource, Mode=OneWay}"
@@ -378,7 +385,8 @@
</Grid>
</StackPanel>
</local:SettingContainer>
<local:SettingContainer x:Uid="Profile_FontAxes"
<local:SettingContainer x:Name="FontAxes"
x:Uid="Profile_FontAxes"
ClearSettingValue="{x:Bind Appearance.ClearFontAxes}"
HasSettingValue="{x:Bind Appearance.HasFontAxes, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.FontAxesOverrideSource, Mode=OneWay}"
@@ -405,7 +413,8 @@
</muxc:DropDownButton>
</StackPanel>
</local:SettingContainer>
<local:SettingContainer x:Uid="Profile_FontFeatures"
<local:SettingContainer x:Name="FontFeatures"
x:Uid="Profile_FontFeatures"
ClearSettingValue="{x:Bind Appearance.ClearFontFeatures}"
HasSettingValue="{x:Bind Appearance.HasFontFeatures, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.FontFeaturesOverrideSource, Mode=OneWay}"
@@ -434,7 +443,8 @@
</local:SettingContainer>
<!-- Builtin Glyphs -->
<local:SettingContainer x:Uid="Profile_EnableBuiltinGlyphs"
<local:SettingContainer x:Name="EnableBuiltinGlyphs"
x:Uid="Profile_EnableBuiltinGlyphs"
ClearSettingValue="{x:Bind Appearance.ClearEnableBuiltinGlyphs}"
HasSettingValue="{x:Bind Appearance.HasEnableBuiltinGlyphs, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.EnableBuiltinGlyphsOverrideSource, Mode=OneWay}">
@@ -443,7 +453,8 @@
</local:SettingContainer>
<!-- Color Glyphs -->
<local:SettingContainer x:Uid="Profile_EnableColorGlyphs"
<local:SettingContainer x:Name="EnableColorGlyphs"
x:Uid="Profile_EnableColorGlyphs"
ClearSettingValue="{x:Bind Appearance.ClearEnableColorGlyphs}"
HasSettingValue="{x:Bind Appearance.HasEnableColorGlyphs, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.EnableColorGlyphsOverrideSource, Mode=OneWay}">
@@ -452,7 +463,8 @@
</local:SettingContainer>
<!-- Retro Terminal Effect -->
<local:SettingContainer x:Uid="Profile_RetroTerminalEffect"
<local:SettingContainer x:Name="RetroTerminalEffect"
x:Uid="Profile_RetroTerminalEffect"
ClearSettingValue="{x:Bind Appearance.ClearRetroTerminalEffect}"
HasSettingValue="{x:Bind Appearance.HasRetroTerminalEffect, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.RetroTerminalEffectOverrideSource, Mode=OneWay}">
@@ -461,7 +473,8 @@
</local:SettingContainer>
<!-- Adjust Indistinguishable Colors -->
<local:SettingContainer x:Uid="Profile_AdjustIndistinguishableColors"
<local:SettingContainer x:Name="AdjustIndistinguishableColors"
x:Uid="Profile_AdjustIndistinguishableColors"
ClearSettingValue="{x:Bind Appearance.ClearAdjustIndistinguishableColors}"
HasSettingValue="{x:Bind Appearance.HasAdjustIndistinguishableColors, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.AdjustIndistinguishableColorsOverrideSource, Mode=OneWay}">
@@ -479,7 +492,8 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Cursor Shape -->
<local:SettingContainer x:Uid="Profile_CursorShape"
<local:SettingContainer x:Name="CursorShape"
x:Uid="Profile_CursorShape"
ClearSettingValue="{x:Bind Appearance.ClearCursorShape}"
HasSettingValue="{x:Bind Appearance.HasCursorShape, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.CursorShapeOverrideSource, Mode=OneWay}">
@@ -491,7 +505,8 @@
</local:SettingContainer>
<!-- Cursor Height -->
<local:SettingContainer x:Uid="Profile_CursorHeight"
<local:SettingContainer x:Name="CursorHeight"
x:Uid="Profile_CursorHeight"
ClearSettingValue="{x:Bind Appearance.ClearCursorHeight}"
HasSettingValue="{x:Bind Appearance.HasCursorHeight, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.CursorHeightOverrideSource, Mode=OneWay}"
@@ -563,7 +578,8 @@
</local:SettingContainer>
<!-- Background Image Stretch Mode -->
<local:SettingContainer x:Uid="Profile_BackgroundImageStretchMode"
<local:SettingContainer x:Name="BackgroundImageStretchMode"
x:Uid="Profile_BackgroundImageStretchMode"
ClearSettingValue="{x:Bind Appearance.ClearBackgroundImageStretchMode}"
HasSettingValue="{x:Bind Appearance.HasBackgroundImageStretchMode, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.BackgroundImageStretchModeOverrideSource, Mode=OneWay}"
@@ -576,7 +592,8 @@
</local:SettingContainer>
<!-- Background Image Alignment -->
<local:SettingContainer x:Uid="Profile_BackgroundImageAlignment"
<local:SettingContainer x:Name="BackgroundImageAlignment"
x:Uid="Profile_BackgroundImageAlignment"
ClearSettingValue="{x:Bind Appearance.ClearBackgroundImageAlignment}"
CurrentValue="{x:Bind Appearance.BackgroundImageAlignmentCurrentValue, Mode=OneWay}"
HasSettingValue="{x:Bind Appearance.HasBackgroundImageAlignment, Mode=OneWay}"
@@ -764,7 +781,8 @@
</local:SettingContainer>
<!-- Background Image Opacity -->
<local:SettingContainer x:Uid="Profile_BackgroundImageOpacity"
<local:SettingContainer x:Name="BackgroundImageOpacity"
x:Uid="Profile_BackgroundImageOpacity"
ClearSettingValue="{x:Bind Appearance.ClearBackgroundImageOpacity}"
HasSettingValue="{x:Bind Appearance.HasBackgroundImageOpacity, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.BackgroundImageOpacityOverrideSource, Mode=OneWay}"
@@ -790,7 +808,8 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Intense is bold, bright -->
<local:SettingContainer x:Uid="Appearance_IntenseTextStyle"
<local:SettingContainer x:Name="IntenseTextStyle"
x:Uid="Appearance_IntenseTextStyle"
ClearSettingValue="{x:Bind Appearance.ClearIntenseTextStyle}"
HasSettingValue="{x:Bind Appearance.HasIntenseTextStyle, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.IntenseTextStyleOverrideSource, Mode=OneWay}">

View File

@@ -4,6 +4,7 @@
#include "pch.h"
#include "ColorSchemes.h"
#include "ColorTableEntry.g.cpp"
#include "NavigateToPageArgs.g.h"
#include "ColorSchemes.g.cpp"
using namespace winrt;
@@ -33,9 +34,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void ColorSchemes::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::ColorSchemesPageViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::ColorSchemesPageViewModel>();
_ViewModel.CurrentPage(ColorSchemesSubPage::Base);
BringIntoViewWhenLoaded(args.ElementToFocus());
_layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();

View File

@@ -37,6 +37,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_OBSERVABLE_PROPERTY(ColorSchemesSubPage, CurrentPage, _propertyChangedHandlers, ColorSchemesSubPage::Base);
WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::ColorSchemeViewModel>, AllColorSchemes, _propertyChangedHandlers, nullptr);
WINRT_PROPERTY(hstring, ElementToFocus);
private:
Editor::ColorSchemeViewModel _CurrentScheme{ nullptr };

View File

@@ -6,6 +6,7 @@
#include "EnumEntry.h"
#include "Compatibility.g.cpp"
#include "CompatibilityViewModel.g.cpp"
#include "NavigateToPageArgs.g.h"
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Microsoft::Terminal::Settings::Model;
@@ -54,7 +55,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Compatibility::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::CompatibilityViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::CompatibilityViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -26,13 +26,15 @@
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Allow Headless -->
<local:SettingContainer x:Uid="Globals_AllowHeadless">
<local:SettingContainer x:Name="AllowHeadless"
x:Uid="Globals_AllowHeadless">
<ToggleSwitch IsOn="{x:Bind ViewModel.AllowHeadless, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Text Measurement -->
<local:SettingContainer x:Uid="Globals_TextMeasurement">
<local:SettingContainer x:Name="TextMeasurement"
x:Uid="Globals_TextMeasurement">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.TextMeasurementList}"
@@ -41,14 +43,16 @@
</local:SettingContainer>
<!-- Debug Features -->
<local:SettingContainer x:Uid="Globals_DebugFeaturesEnabled"
<local:SettingContainer x:Name="DebugFeaturesEnabled"
x:Uid="Globals_DebugFeaturesEnabled"
Visibility="{x:Bind ViewModel.DebugFeaturesAvailable}">
<ToggleSwitch IsOn="{x:Bind ViewModel.DebugFeaturesEnabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Reset Application State -->
<local:SettingContainer x:Uid="Settings_ResetApplicationState">
<local:SettingContainer x:Name="ResetApplicationState"
x:Uid="Settings_ResetApplicationState">
<Button x:Uid="Settings_ResetApplicationStateButton"
Style="{StaticResource DeleteButtonStyle}">
<Button.Flyout>
@@ -69,7 +73,8 @@
</local:SettingContainer>
<!-- Reset to Default Settings -->
<local:SettingContainer x:Uid="Settings_ResetToDefaultSettings">
<local:SettingContainer x:Name="ResetToDefaultSettings"
x:Uid="Settings_ResetToDefaultSettings">
<Button x:Uid="Settings_ResetToDefaultSettingsButton"
Style="{StaticResource DeleteButtonStyle}">
<Button.Flyout>

View File

@@ -4,6 +4,7 @@
#include "pch.h"
#include "EditColorScheme.h"
#include "EditColorScheme.g.cpp"
#include "NavigateToPageArgs.g.h"
using namespace winrt;
using namespace winrt::Windows::UI;
@@ -38,7 +39,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void EditColorScheme::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::ColorSchemeViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::ColorSchemeViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
const auto schemeName = _ViewModel.Name();
NameBox().Text(schemeName);

View File

@@ -200,7 +200,8 @@
</Grid>
</Border>
<local:SettingContainer x:Uid="ColorScheme_InboxSchemeDuplicate"
<local:SettingContainer x:Name="InboxSchemeDuplicate"
x:Uid="ColorScheme_InboxSchemeDuplicate"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.IsEditable), Mode=OneWay}">
<Button x:Name="DuplicateSchemeButton"
x:Uid="ColorScheme_DuplicateButton"
@@ -208,7 +209,8 @@
Style="{StaticResource BrowseButtonStyle}" />
</local:SettingContainer>
<local:SettingContainer x:Uid="ColorScheme_ColorsHeader"
<local:SettingContainer x:Name="ColorsHeader"
x:Uid="ColorScheme_ColorsHeader"
StartExpanded="True"
Style="{StaticResource ExpanderSettingContainerStyle}"
Visibility="{x:Bind ViewModel.IsEditable, Mode=OneWay}">

View File

@@ -3,11 +3,13 @@
#include "pch.h"
#include "Extensions.h"
#include "NavigateToPageArgs.g.h"
#include "Extensions.g.cpp"
#include "ExtensionPackageViewModel.g.cpp"
#include "ExtensionsViewModel.g.cpp"
#include "FragmentProfileViewModel.g.cpp"
#include "ExtensionPackageTemplateSelector.g.cpp"
#include "NavConstants.h"
#include "..\WinRTUtils\inc\Utils.h"
@@ -33,11 +35,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Extensions::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::ExtensionsViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::ExtensionsViewModel>();
auto vmImpl = get_self<ExtensionsViewModel>(_ViewModel);
vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector);
vmImpl->LazyLoadExtensions();
BringIntoViewWhenLoaded(args.ElementToFocus());
if (vmImpl->IsExtensionView())
{
const auto currentPkgVM = vmImpl->CurrentExtensionPackage();
@@ -452,6 +458,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
hstring ExtensionPackageViewModel::DisplayName() const noexcept
{
// Fragment extensions may not have a DisplayName, so fall back to Source
const auto displayName = _package.DisplayName();
return displayName.empty() ? _package.Source() : displayName;
}
hstring ExtensionPackageViewModel::Icon() const noexcept
{
// Fragment extensions may not have an Icon, so fall back to the extensions nav icon glyph
const auto icon = _package.Icon();
return icon.empty() ? NavTagIconMap[extensionsTag] : icon;
}
hstring ExtensionPackageViewModel::Scope() const noexcept
{
return _package.Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem");

View File

@@ -93,6 +93,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void UpdateSettings(const Model::CascadiaSettings& settings);
Model::ExtensionPackage Package() const noexcept { return _package; }
hstring DisplayName() const noexcept;
hstring Icon() const noexcept;
hstring Scope() const noexcept;
bool Enabled() const;
void Enabled(bool val);

View File

@@ -40,6 +40,8 @@ namespace Microsoft.Terminal.Settings.Editor
[default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Microsoft.Terminal.Settings.Model.ExtensionPackage Package { get; };
String DisplayName { get; };
String Icon { get; };
Boolean Enabled;
String Scope { get; };
String AccessibleName { get; };

View File

@@ -500,7 +500,8 @@
<muxc:Expander.Content>
<StackPanel>
<!-- Scope -->
<local:SettingContainer x:Uid="Extensions_Scope"
<local:SettingContainer x:Name="Scope"
x:Uid="Extensions_Scope"
Content="{x:Bind ViewModel.CurrentExtensionPackage.Scope, Mode=OneWay}"
IsTabStop="False"
Style="{StaticResource SettingContainerWithTextContent}" />

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "GlobalAppearance.h"
#include "NavigateToPageArgs.g.h"
#include "GlobalAppearance.g.cpp"
#include <WtExeUtils.h>
@@ -23,7 +24,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void GlobalAppearance::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::GlobalAppearanceViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::GlobalAppearanceViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -5,7 +5,7 @@ import "GlobalAppearanceViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass GlobalAppearance : Windows.UI.Xaml.Controls.Page
runtimeclass GlobalAppearance : Windows.UI.Xaml.Controls.Page
{
GlobalAppearance();
GlobalAppearanceViewModel ViewModel { get; };

View File

@@ -28,7 +28,8 @@
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Theme -->
<local:SettingContainer x:Uid="Globals_Theme">
<local:SettingContainer x:Name="Theme"
x:Uid="Globals_Theme">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemsSource="{x:Bind ViewModel.ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.CurrentTheme, Mode=TwoWay}"
@@ -42,7 +43,8 @@
</local:SettingContainer>
<!-- Position of new tab -->
<local:SettingContainer x:Uid="Globals_NewTabPosition">
<local:SettingContainer x:Name="NewTabPosition"
x:Uid="Globals_NewTabPosition">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.NewTabPositionList, Mode=OneWay}"
@@ -51,45 +53,52 @@
</local:SettingContainer>
<!-- Show Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitlebar">
<local:SettingContainer x:Name="ShowTitlebar"
x:Uid="Globals_ShowTitlebar">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTabsInTitlebar, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}"
Toggled="{x:Bind ViewModel.ShowTitlebarToggled}" />
</local:SettingContainer>
<!-- Always show tabs -->
<local:SettingContainer x:Uid="Globals_AlwaysShowTabs">
<local:SettingContainer x:Name="AlwaysShowTabs"
x:Uid="Globals_AlwaysShowTabs">
<ToggleSwitch IsEnabled="{x:Bind mtu:Converters.InvertBoolean(ViewModel.ShowTabsInTitlebar), Mode=OneWay}"
IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show tabs in full screen -->
<local:SettingContainer x:Uid="Globals_ShowTabsFullscreen">
<local:SettingContainer x:Name="ShowTabsFullscreen"
x:Uid="Globals_ShowTabsFullscreen">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTabsFullscreen, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show Acrylic in Tab Row -->
<local:SettingContainer x:Uid="Globals_AcrylicTabRow">
<local:SettingContainer x:Name="AcrylicTabRow"
x:Uid="Globals_AcrylicTabRow">
<ToggleSwitch IsOn="{x:Bind ViewModel.UseAcrylicInTabRow, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show Title in Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitleInTitlebar">
<local:SettingContainer x:Name="ShowTitleInTitlebar"
x:Uid="Globals_ShowTitleInTitlebar">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTitleInTitlebar, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Always on Top -->
<local:SettingContainer x:Uid="Globals_AlwaysOnTop">
<local:SettingContainer x:Name="AlwaysOnTop"
x:Uid="Globals_AlwaysOnTop">
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysOnTop, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Tab Width Mode -->
<local:SettingContainer x:Uid="Globals_TabWidthMode">
<local:SettingContainer x:Name="TabWidthMode"
x:Uid="Globals_TabWidthMode">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.TabWidthModeList, Mode=OneWay}"
@@ -99,37 +108,43 @@
<!-- Disable Animations -->
<!-- NOTE: the UID is "DisablePaneAnimationsReversed" not "DisablePaneAnimations". See GH#9124 for more details. -->
<local:SettingContainer x:Uid="Globals_DisableAnimationsReversed">
<local:SettingContainer x:Name="DisableAnimations"
x:Uid="Globals_DisableAnimationsReversed">
<ToggleSwitch IsOn="{x:Bind ViewModel.InvertedDisableAnimations, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Always Show Notification Icon -->
<local:SettingContainer x:Uid="Globals_AlwaysShowNotificationIcon">
<local:SettingContainer x:Name="AlwaysShowNotificationIcon"
x:Uid="Globals_AlwaysShowNotificationIcon">
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysShowNotificationIcon, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Minimize To Notification Area -->
<local:SettingContainer x:Uid="Globals_MinimizeToNotificationArea">
<local:SettingContainer x:Name="MinimizeToNotificationArea"
x:Uid="Globals_MinimizeToNotificationArea">
<ToggleSwitch IsOn="{x:Bind ViewModel.MinimizeToNotificationArea, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Automatically hide window -->
<local:SettingContainer x:Uid="Globals_AutoHideWindow">
<local:SettingContainer x:Name="AutoHideWindow"
x:Uid="Globals_AutoHideWindow">
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoHideWindow, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show Admin Shield -->
<local:SettingContainer x:Uid="Globals_ShowAdminShield">
<local:SettingContainer x:Name="ShowAdminShield"
x:Uid="Globals_ShowAdminShield">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowAdminShield, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Enable Unfocused Acrylic -->
<local:SettingContainer x:Uid="Globals_EnableUnfocusedAcrylic">
<local:SettingContainer x:Name="EnableUnfocusedAcrylic"
x:Uid="Globals_EnableUnfocusedAcrylic">
<ToggleSwitch IsOn="{x:Bind ViewModel.EnableUnfocusedAcrylic, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>

View File

@@ -0,0 +1,330 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "IconPicker.h"
#include "IconPicker.g.cpp"
#include <LibraryResources.h>
#include "SegoeFluentIconList.h"
#include "../../types/inc/utils.hpp"
#include "../WinRTUtils/inc/Utils.h"
using namespace winrt;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Media;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Microsoft::UI::Xaml::Controls;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static constexpr std::wstring_view HideIconValue{ L"none" };
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_BuiltInIcons{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_IconTypes{ nullptr };
DependencyProperty IconPicker::_CurrentIconPathProperty{ nullptr };
DependencyProperty IconPicker::_WindowRootProperty{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::BuiltInIcons() noexcept
{
if (!_BuiltInIcons)
{
// lazy load the built-in icons
std::vector<Editor::EnumEntry> builtInIcons;
for (auto& [val, name] : s_SegoeFluentIcons)
{
builtInIcons.emplace_back(make<EnumEntry>(hstring{ name }, box_value(val)));
}
_BuiltInIcons = single_threaded_observable_vector<Editor::EnumEntry>(std::move(builtInIcons));
}
return _BuiltInIcons;
}
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::IconTypes() noexcept
{
if (!_IconTypes)
{
// lazy load the icon types
std::vector<Editor::EnumEntry> iconTypes;
iconTypes.reserve(4);
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeNone"), box_value(IconType::None)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeFontIcon"), box_value(IconType::FontIcon)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeEmoji"), box_value(IconType::Emoji)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeImage"), box_value(IconType::Image)));
_IconTypes = winrt::single_threaded_observable_vector<Editor::EnumEntry>(std::move(iconTypes));
}
return _IconTypes;
}
IconPicker::IconPicker()
{
_InitializeProperties();
InitializeComponent();
_DeduceCurrentIconType();
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
const auto propertyName{ args.PropertyName() };
// "CurrentIconPath" changes are handled by _OnCurrentIconPathChanged()
if (propertyName == L"CurrentIconType")
{
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingNoIcon" });
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingBuiltInIcon" });
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingEmojiIcon" });
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingImageIcon" });
}
else if (propertyName == L"CurrentBuiltInIcon")
{
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
}
else if (propertyName == L"CurrentEmojiIcon")
{
CurrentIconPath(CurrentEmojiIcon());
}
});
}
void IconPicker::_InitializeProperties()
{
// Initialize any dependency properties here.
// This performs a lazy load on these properties, instead of
// initializing them when the DLL loads.
if (!_CurrentIconPathProperty)
{
_CurrentIconPathProperty =
DependencyProperty::Register(
L"CurrentIconPath",
xaml_typename<hstring>(),
xaml_typename<Editor::IconPicker>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &IconPicker::_OnCurrentIconPathChanged } });
}
if (!_WindowRootProperty)
{
_WindowRootProperty =
DependencyProperty::Register(
L"WindowRoot",
xaml_typename<IHostedInWindow>(),
xaml_typename<Editor::IconPicker>(),
PropertyMetadata{ nullptr });
}
}
void IconPicker::_OnCurrentIconPathChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
d.as<IconPicker>()->_DeduceCurrentIconType();
}
safe_void_coroutine IconPicker::Icon_Click(const IInspectable&, const RoutedEventArgs&)
{
auto lifetime = get_strong();
const auto parentHwnd{ reinterpret_cast<HWND>(WindowRoot().GetHostingWindow()) };
auto file = co_await OpenImagePicker(parentHwnd);
if (!file.empty())
{
CurrentIconPath(file);
}
}
void IconPicker::BuiltInIconPicker_GotFocus(const IInspectable& sender, const RoutedEventArgs& /*e*/)
{
_updateIconFilter({});
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
}
void IconPicker::BuiltInIconPicker_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& e)
{
const auto iconEntry = unbox_value_or<Editor::EnumEntry>(e.ChosenSuggestion(), nullptr);
if (!iconEntry)
{
return;
}
CurrentBuiltInIcon(iconEntry);
}
void IconPicker::BuiltInIconPicker_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& e)
{
if (e.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
{
return;
}
std::wstring_view filter{ sender.Text() };
filter = til::trim(filter, L' ');
_updateIconFilter(filter);
}
void IconPicker::_updateIconFilter(std::wstring_view filter)
{
if (_iconFilter != filter)
{
_filteredBuiltInIcons = nullptr;
_iconFilter = filter;
_updateFilteredIconList();
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredBuiltInIconList" });
}
}
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::FilteredBuiltInIconList()
{
if (!_filteredBuiltInIcons)
{
_updateFilteredIconList();
}
return _filteredBuiltInIcons;
}
void IconPicker::_updateFilteredIconList()
{
_filteredBuiltInIcons = BuiltInIcons();
if (_iconFilter.empty())
{
return;
}
// Find matching icons and populate the filtered list
std::vector<Editor::EnumEntry> filtered;
filtered.reserve(_filteredBuiltInIcons.Size());
for (const auto& icon : _filteredBuiltInIcons)
{
if (til::contains_linguistic_insensitive(icon.EnumName(), _iconFilter))
{
filtered.emplace_back(icon);
}
}
_filteredBuiltInIcons = winrt::single_threaded_observable_vector(std::move(filtered));
}
void IconPicker::CurrentIconType(const Windows::Foundation::IInspectable& value)
{
if (_currentIconType != value)
{
// Switching from...
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::Image)
{
// Stash the current value of Icon. If the user
// switches out of then back to IconType::Image, we want
// the path that we display in the text box to remain unchanged.
_lastIconPath = CurrentIconPath();
}
// Set the member here instead of after setting Icon() below!
// We have an Icon property changed handler defined for when we discard changes.
// Inadvertently, that means that we call this setter again.
// Setting the member here means that we early exit at the beginning of the function
// because _currentIconType == value.
_currentIconType = value;
// Switched to...
switch (unbox_value<IconType>(value.as<Editor::EnumEntry>().EnumValue()))
{
case IconType::None:
{
CurrentIconPath(winrt::hstring{ HideIconValue });
break;
}
case IconType::Image:
{
if (!_lastIconPath.empty())
{
// Conversely, if we switch to Image,
// retrieve that saved value and apply it
CurrentIconPath(_lastIconPath);
}
break;
}
case IconType::FontIcon:
{
if (_CurrentBuiltInIcon)
{
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
}
break;
}
case IconType::Emoji:
{
// Don't set Icon here!
// Clear out the text box so we direct the user to use the emoji picker.
CurrentEmojiIcon({});
}
}
// We're not using the VM's Icon() setter above,
// so notify HasIcon changed manually
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"HasIcon" });
}
}
bool IconPicker::UsingNoIcon() const
{
return _currentIconType == IconTypes().GetAt(0);
}
bool IconPicker::UsingBuiltInIcon() const
{
return _currentIconType == IconTypes().GetAt(1);
}
bool IconPicker::UsingEmojiIcon() const
{
return _currentIconType == IconTypes().GetAt(2);
}
bool IconPicker::UsingImageIcon() const
{
return _currentIconType == IconTypes().GetAt(3);
}
void IconPicker::_DeduceCurrentIconType()
{
const auto icon = CurrentIconPath();
if (icon.empty() || icon == HideIconValue)
{
_currentIconType = IconTypes().GetAt(0);
}
else if (icon.size() == 1 && (L'\uE700' <= til::at(icon, 0) && til::at(icon, 0) <= L'\uF8B3'))
{
_currentIconType = IconTypes().GetAt(1);
_DeduceCurrentBuiltInIcon();
}
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(icon))
{
// We already did a range check for MDL2 Assets in the previous one,
// so if we're out of that range but still short, assume we're an emoji
_currentIconType = IconTypes().GetAt(2);
}
else
{
_currentIconType = IconTypes().GetAt(3);
}
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
}
void IconPicker::_DeduceCurrentBuiltInIcon()
{
const auto icon = CurrentIconPath();
for (uint32_t i = 0; i < BuiltInIcons().Size(); i++)
{
const auto& builtIn = BuiltInIcons().GetAt(i);
if (icon == unbox_value<hstring>(builtIn.EnumValue()))
{
CurrentBuiltInIcon(builtIn);
return;
}
}
CurrentBuiltInIcon(BuiltInIcons().GetAt(0));
}
WUX::Controls::IconSource IconPicker::BuiltInIconConverter(const IInspectable& iconVal)
{
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "IconPicker.g.h"
#include "EnumEntry.h"
#include "Utils.h"
#include "cppwinrt_utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct IconPicker : public HasScrollViewer<IconPicker>, IconPickerT<IconPicker>
{
public:
IconPicker();
static constexpr std::wstring_view HideIconValue{ L"none" };
static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> BuiltInIcons() noexcept;
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconTypes() noexcept;
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> FilteredBuiltInIconList();
safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void BuiltInIconPicker_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void BuiltInIconPicker_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& e);
void BuiltInIconPicker_QuerySubmitted(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& e);
Windows::Foundation::IInspectable CurrentIconType() const noexcept { return _currentIconType; }
void CurrentIconType(const Windows::Foundation::IInspectable& value);
bool UsingNoIcon() const;
bool UsingBuiltInIcon() const;
bool UsingEmojiIcon() const;
bool UsingImageIcon() const;
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(Editor::EnumEntry, CurrentBuiltInIcon, PropertyChanged.raise, nullptr);
DEPENDENCY_PROPERTY(hstring, CurrentIconPath);
DEPENDENCY_PROPERTY(IHostedInWindow, WindowRoot);
private:
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _BuiltInIcons;
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _IconTypes;
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _filteredBuiltInIcons;
std::wstring _iconFilter;
Windows::Foundation::IInspectable _currentIconType{};
winrt::hstring _lastIconPath;
static void _InitializeProperties();
static void _OnCurrentIconPathChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
void _DeduceCurrentIconType();
void _DeduceCurrentBuiltInIcon();
void _updateIconFilter(std::wstring_view filter);
void _updateFilteredIconList();
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(IconPicker);
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "EnumEntry.idl";
import "MainPage.idl";
namespace Microsoft.Terminal.Settings.Editor
{
enum IconType
{
None = 0,
FontIcon,
Image,
Emoji
};
[default_interface] runtimeclass IconPicker : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
IconPicker();
IInspectable CurrentIconType;
Windows.Foundation.Collections.IObservableVector<EnumEntry> IconTypes { get; };
Boolean UsingBuiltInIcon { get; };
Boolean UsingEmojiIcon { get; };
Boolean UsingImageIcon { get; };
String CurrentEmojiIcon;
EnumEntry CurrentBuiltInIcon;
Windows.Foundation.Collections.IObservableVector<EnumEntry> BuiltInIcons { get; };
Windows.Foundation.Collections.IObservableVector<EnumEntry> FilteredBuiltInIconList { get; };
static Windows.UI.Xaml.Controls.IconSource BuiltInIconConverter(IInspectable iconVal);
String CurrentIconPath;
static Windows.UI.Xaml.DependencyProperty CurrentIconPathProperty { get; };
IHostedInWindow WindowRoot;
static Windows.UI.Xaml.DependencyProperty WindowRootProperty { get; };
}
}

View File

@@ -0,0 +1,100 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<UserControl x:Class="Microsoft.Terminal.Settings.Editor.IconPicker"
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"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<!-- Icon Type -->
<ComboBox x:Uid="IconPicker_IconType"
Grid.Column="0"
ItemsSource="{x:Bind IconTypes}"
SelectedItem="{x:Bind CurrentIconType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="local:EnumEntry">
<TextBlock Text="{x:Bind EnumName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Built-In Icon -->
<AutoSuggestBox x:Uid="IconPicker_BuiltInIcon"
Grid.Column="1"
GotFocus="BuiltInIconPicker_GotFocus"
ItemsSource="{x:Bind FilteredBuiltInIconList, Mode=OneWay}"
QuerySubmitted="BuiltInIconPicker_QuerySubmitted"
Text="{x:Bind CurrentBuiltInIcon.EnumName, Mode=OneWay}"
TextBoxStyle="{StaticResource TextBoxSettingStyle}"
TextChanged="BuiltInIconPicker_TextChanged"
Visibility="{x:Bind UsingBuiltInIcon, Mode=OneWay}">
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="local:EnumEntry">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<IconSourceElement Grid.Column="0"
Width="16"
Height="16"
IconSource="{x:Bind local:IconPicker.BuiltInIconConverter(EnumValue), Mode=OneTime}" />
<TextBlock Grid.Column="1"
Text="{x:Bind EnumName}" />
</Grid>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
<!-- Image (File) Icon -->
<TextBox x:Uid="IconPicker_ImagePathBox"
Grid.Column="1"
MaxWidth="Infinity"
HorizontalAlignment="Stretch"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind CurrentIconPath, Mode=TwoWay}"
Visibility="{x:Bind UsingImageIcon, Mode=OneWay}" />
<Button x:Uid="IconPicker_IconBrowse"
Grid.Column="2"
Margin="0"
VerticalAlignment="Top"
Click="Icon_Click"
Style="{StaticResource BrowseButtonStyle}"
Visibility="{x:Bind UsingImageIcon, Mode=OneWay}" />
<!-- Emoji Icon -->
<TextBox x:Uid="IconPicker_EmojiBox"
Grid.Column="1"
MaxWidth="Infinity"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind CurrentEmojiIcon, Mode=TwoWay}"
Visibility="{x:Bind UsingEmojiIcon, Mode=OneWay}" />
</Grid>
</UserControl>

View File

@@ -4,6 +4,7 @@
#include "pch.h"
#include "Interaction.h"
#include "Interaction.g.cpp"
#include "NavigateToPageArgs.g.h"
#include "EnumEntry.h"
@@ -21,7 +22,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Interaction::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::InteractionViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::InteractionViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -27,13 +27,15 @@
<StackPanel>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Copy On Select -->
<local:SettingContainer x:Uid="Globals_CopyOnSelect">
<local:SettingContainer x:Name="CopyOnSelect"
x:Uid="Globals_CopyOnSelect">
<ToggleSwitch IsOn="{x:Bind ViewModel.CopyOnSelect, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Copy Format -->
<local:SettingContainer x:Uid="Globals_CopyFormat">
<local:SettingContainer x:Name="CopyFormat"
x:Uid="Globals_CopyFormat">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.CopyFormatList, Mode=OneWay}"
@@ -42,19 +44,22 @@
</local:SettingContainer>
<!-- Trim Block Selection -->
<local:SettingContainer x:Uid="Globals_TrimBlockSelection">
<local:SettingContainer x:Name="TrimBlockSelection"
x:Uid="Globals_TrimBlockSelection">
<ToggleSwitch IsOn="{x:Bind ViewModel.TrimBlockSelection, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Trim Paste -->
<local:SettingContainer x:Uid="Globals_TrimPaste">
<local:SettingContainer x:Name="TrimPaste"
x:Uid="Globals_TrimPaste">
<ToggleSwitch IsOn="{x:Bind ViewModel.TrimPaste, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Word Delimiters -->
<local:SettingContainer x:Uid="Globals_WordDelimiters"
<local:SettingContainer x:Name="WordDelimiters"
x:Uid="Globals_WordDelimiters"
CurrentValue="{x:Bind ViewModel.WordDelimiters, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<TextBox IsSpellCheckEnabled="False"
@@ -63,13 +68,15 @@
</local:SettingContainer>
<!-- Snap On Resize -->
<local:SettingContainer x:Uid="Globals_SnapToGridOnResize">
<local:SettingContainer x:Name="SnapToGridOnResize"
x:Uid="Globals_SnapToGridOnResize">
<ToggleSwitch IsOn="{x:Bind ViewModel.SnapToGridOnResize, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Tab Switcher Mode -->
<local:SettingContainer x:Uid="Globals_TabSwitcherMode">
<local:SettingContainer x:Name="TabSwitcherMode"
x:Uid="Globals_TabSwitcherMode">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.TabSwitcherModeList}"
@@ -78,31 +85,36 @@
</local:SettingContainer>
<!-- Focus Follow Mouse Mode -->
<local:SettingContainer x:Uid="Globals_FocusFollowMouse">
<local:SettingContainer x:Name="FocusFollowMouse"
x:Uid="Globals_FocusFollowMouse">
<ToggleSwitch IsOn="{x:Bind ViewModel.FocusFollowMouse, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Enable Font Size Changes with Scrolling -->
<local:SettingContainer x:Uid="Globals_ScrollToZoom">
<local:SettingContainer x:Name="ScrollToZoom"
x:Uid="Globals_ScrollToZoom">
<ToggleSwitch IsOn="{x:Bind ViewModel.ScrollToZoom, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Enable Window Opacity Changes with Scrolling -->
<local:SettingContainer x:Uid="Globals_ScrollToChangeOpacity">
<local:SettingContainer x:Name="ScrollToChangeOpacity"
x:Uid="Globals_ScrollToChangeOpacity">
<ToggleSwitch IsOn="{x:Bind ViewModel.ScrollToChangeOpacity, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Detect URLs -->
<local:SettingContainer x:Uid="Globals_DetectURLs">
<local:SettingContainer x:Name="DetectURLs"
x:Uid="Globals_DetectURLs">
<ToggleSwitch IsOn="{x:Bind ViewModel.DetectURLs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Search Web Default Query URL -->
<local:SettingContainer x:Uid="Globals_SearchWebDefaultQueryUrl"
<local:SettingContainer x:Name="SearchWebDefaultQueryUrl"
x:Uid="Globals_SearchWebDefaultQueryUrl"
CurrentValue="{x:Bind ViewModel.SearchWebDefaultQueryUrl, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<TextBox IsSpellCheckEnabled="False"
@@ -111,7 +123,8 @@
</local:SettingContainer>
<!-- Enable Color Selection -->
<local:SettingContainer x:Uid="Globals_EnableColorSelection">
<local:SettingContainer x:Name="EnableColorSelection"
x:Uid="Globals_EnableColorSelection">
<ToggleSwitch IsOn="{x:Bind ViewModel.EnableColorSelection, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
@@ -123,25 +136,29 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Close All Tabs Warning -->
<local:SettingContainer x:Uid="Globals_ConfirmCloseAllTabs">
<local:SettingContainer x:Name="ConfirmCloseAllTabs"
x:Uid="Globals_ConfirmCloseAllTabs">
<ToggleSwitch IsOn="{x:Bind ViewModel.ConfirmCloseAllTabs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Input Service Warning -->
<local:SettingContainer x:Uid="Globals_InputServiceWarning">
<local:SettingContainer x:Name="InputServiceWarning"
x:Uid="Globals_InputServiceWarning">
<ToggleSwitch IsOn="{x:Bind ViewModel.InputServiceWarning, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Large Paste Warning -->
<local:SettingContainer x:Uid="Globals_WarnAboutLargePaste">
<local:SettingContainer x:Name="WarnAboutLargePaste"
x:Uid="Globals_WarnAboutLargePaste">
<ToggleSwitch IsOn="{x:Bind ViewModel.WarnAboutLargePaste, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Multi Line Paste Warning -->
<local:SettingContainer x:Uid="Globals_WarnAboutMultiLinePaste">
<local:SettingContainer x:Name="WarnAboutMultiLinePaste"
x:Uid="Globals_WarnAboutMultiLinePaste">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind WarnAboutMultiLinePasteList}"

View File

@@ -38,7 +38,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Launch::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::LaunchViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::LaunchViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
auto innerViewModel{ winrt::get_self<Editor::implementation::LaunchViewModel>(_ViewModel) };
/* coroutine dispatch */ innerViewModel->PrepareStartOnUserLoginSettings();

View File

@@ -44,9 +44,9 @@
<StackPanel>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Default Profile -->
<local:SettingContainer x:Uid="Globals_DefaultProfile">
<ComboBox x:Name="DefaultProfile"
ItemsSource="{x:Bind ViewModel.DefaultProfiles}"
<local:SettingContainer x:Name="DefaultProfile"
x:Uid="Globals_DefaultProfile">
<ComboBox ItemsSource="{x:Bind ViewModel.DefaultProfiles}"
SelectedItem="{x:Bind ViewModel.CurrentDefaultProfile, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}">
<ComboBox.ItemTemplate>
@@ -141,7 +141,8 @@
</local:SettingContainer>
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language">
<local:SettingContainer x:Name="Language"
x:Uid="Globals_Language">
<ComboBox ItemsSource="{x:Bind ViewModel.LanguageList}"
SelectedItem="{x:Bind ViewModel.CurrentLanguage, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}">
@@ -154,7 +155,8 @@
</local:SettingContainer>
<!-- Language -->
<local:SettingContainer x:Uid="Globals_DefaultInputScope">
<local:SettingContainer x:Name="DefaultInputScope"
x:Uid="Globals_DefaultInputScope">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.DefaultInputScopeList}"
@@ -163,7 +165,8 @@
</local:SettingContainer>
<!-- Start on User Login -->
<local:SettingContainer x:Uid="Globals_StartOnUserLogin"
<local:SettingContainer x:Name="StartOnUserLogin"
x:Uid="Globals_StartOnUserLogin"
HelpText="{x:Bind ViewModel.StartOnUserLoginStatefulHelpText, Mode=OneWay}"
Visibility="{x:Bind ViewModel.StartOnUserLoginAvailable, Mode=OneTime}">
<ToggleSwitch IsEnabled="{x:Bind ViewModel.StartOnUserLoginConfigurable, Mode=OneWay}"
@@ -172,7 +175,8 @@
</local:SettingContainer>
<!-- First Window Behavior -->
<local:SettingContainer x:Uid="Globals_FirstWindowPreference">
<local:SettingContainer x:Name="FirstWindowPreference"
x:Uid="Globals_FirstWindowPreference">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.FirstWindowPreferenceList}"
@@ -181,7 +185,8 @@
</local:SettingContainer>
<!-- Windowing Behavior -->
<local:SettingContainer x:Uid="Globals_WindowingBehavior">
<local:SettingContainer x:Name="WindowingBehavior"
x:Uid="Globals_WindowingBehavior">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.WindowingBehaviorList}"
@@ -190,7 +195,8 @@
</local:SettingContainer>
<!-- Launch Size -->
<local:SettingContainer x:Uid="Globals_LaunchSize"
<local:SettingContainer x:Name="LaunchSize"
x:Uid="Globals_LaunchSize"
CurrentValue="{x:Bind ViewModel.LaunchSizeCurrentValue, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<Grid ColumnSpacing="12"
@@ -233,7 +239,8 @@
</local:SettingContainer>
<!-- Launch Parameters -->
<local:SettingContainer x:Uid="Globals_LaunchParameters"
<local:SettingContainer x:Name="LaunchParameters"
x:Uid="Globals_LaunchParameters"
CurrentValue="{x:Bind ViewModel.LaunchParametersCurrentValue, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<Grid RowSpacing="8">

View File

@@ -15,14 +15,18 @@
#include "GlobalAppearance.h"
#include "GlobalAppearanceViewModel.h"
#include "ColorSchemes.h"
#include "EditColorScheme.h"
#include "AddProfile.h"
#include "InteractionViewModel.h"
#include "LaunchViewModel.h"
#include "NewTabMenuViewModel.h"
#include "NewTabMenu.h"
#include "NavConstants.h"
#include "..\types\inc\utils.hpp"
#include <..\WinRTUtils\inc\Utils.h>
#include <dwmapi.h>
#include <fmt/compile.h>
namespace winrt
{
@@ -38,29 +42,45 @@ using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::Foundation::Collections;
static const std::wstring_view openJsonTag{ L"OpenJson_Nav" };
static const std::wstring_view launchTag{ L"Launch_Nav" };
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 extensionsTag{ L"Extensions_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" };
static const std::wstring_view globalAppearanceTag{ L"GlobalAppearance_Nav" };
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static WUX::Controls::FontIcon _fontIconForNavTag(const std::wstring_view navTag)
{
WUX::Controls::FontIcon icon{};
icon.Glyph(NavTagIconMap[navTag]);
icon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(16);
return icon;
}
static Editor::ProfileViewModel _viewModelForProfile(const Model::Profile& profile, const Model::CascadiaSettings& appSettings, const Windows::UI::Core::CoreDispatcher& dispatcher)
{
return winrt::make<implementation::ProfileViewModel>(profile, appSettings, dispatcher);
}
static ProfileSubPage ProfileSubPageFromBreadcrumb(BreadcrumbSubPage subPage)
{
switch (subPage)
{
case BreadcrumbSubPage::None:
return ProfileSubPage::Base;
case BreadcrumbSubPage::Profile_Appearance:
return ProfileSubPage::Appearance;
case BreadcrumbSubPage::Profile_Terminal:
return ProfileSubPage::Terminal;
case BreadcrumbSubPage::Profile_Advanced:
return ProfileSubPage::Advanced;
default:
// This should never happen
assert(false);
return ProfileSubPage::Base;
}
}
MainPage::MainPage(const CascadiaSettings& settings) :
_settingsSource{ settings },
_settingsClone{ settings.Copy() }
_settingsClone{ settings.Copy() },
_profileVMs{ single_threaded_observable_vector<Editor::ProfileViewModel>() }
{
InitializeComponent();
_UpdateBackgroundForMica();
@@ -83,7 +103,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
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);
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this));
}
});
@@ -92,16 +112,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto settingName{ args.PropertyName() };
if (settingName == L"CurrentPage")
{
// extract ElementToFocus and clear it; we only want to use it once
auto vmImpl = get_self<ColorSchemesPageViewModel>(_colorSchemesPageVM);
const auto elementToFocus = vmImpl->ElementToFocus();
vmImpl->ElementToFocus({});
const auto currentScheme = _colorSchemesPageVM.CurrentScheme();
if (_colorSchemesPageVM.CurrentPage() == ColorSchemesSubPage::EditColorScheme && currentScheme)
{
contentFrame().Navigate(xaml_typename<Editor::EditColorScheme>(), currentScheme);
contentFrame().Navigate(xaml_typename<Editor::EditColorScheme>(), winrt::make<NavigateToPageArgs>(currentScheme, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(colorSchemesTag), currentScheme.Name(), BreadcrumbSubPage::ColorSchemes_Edit);
_breadcrumbs.Append(crumb);
}
else if (_colorSchemesPageVM.CurrentPage() == ColorSchemesSubPage::Base)
{
_Navigate(winrt::hstring{ colorSchemesTag }, BreadcrumbSubPage::None);
_Navigate(winrt::hstring{ colorSchemesTag }, BreadcrumbSubPage::None, elementToFocus);
}
}
else if (settingName == L"CurrentSchemeName")
@@ -141,9 +166,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (const auto& currentExtensionPackage = _extensionsVM.CurrentExtensionPackage())
{
const auto& pkg = currentExtensionPackage.Package();
const auto label = pkg.DisplayName().empty() ? pkg.Source() : pkg.DisplayName();
const auto crumb = winrt::make<Breadcrumb>(box_value(currentExtensionPackage), label, BreadcrumbSubPage::Extensions_Extension);
const auto crumb = winrt::make<Breadcrumb>(box_value(currentExtensionPackage), currentExtensionPackage.DisplayName(), BreadcrumbSubPage::Extensions_Extension);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
}
@@ -154,7 +177,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto crumb = winrt::make<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToPageArgs>(_extensionsVM, *this));
}
});
@@ -162,6 +185,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// that VM into the appearance VMs within the profiles
_InitializeProfilesList();
// Apply icons to static nav items
LaunchNavItem().Icon(_fontIconForNavTag(launchTag));
InteractionNavItem().Icon(_fontIconForNavTag(interactionTag));
AppearanceNavItem().Icon(_fontIconForNavTag(globalAppearanceTag));
ColorSchemesNavItem().Icon(_fontIconForNavTag(colorSchemesTag));
RenderingNavItem().Icon(_fontIconForNavTag(renderingTag));
CompatibilityNavItem().Icon(_fontIconForNavTag(compatibilityTag));
ActionsNavItem().Icon(_fontIconForNavTag(actionsTag));
NewTabMenuNavItem().Icon(_fontIconForNavTag(newTabMenuTag));
ExtensionsNavItem().Icon(_fontIconForNavTag(extensionsTag));
BaseLayerMenuItem().Icon(_fontIconForNavTag(globalProfileTag));
OpenJsonNavItem().Icon(_fontIconForNavTag(openJsonTag));
Automation::AutomationProperties::SetHelpText(SaveButton(), RS_(L"Settings_SaveSettingsButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
Automation::AutomationProperties::SetHelpText(ResetButton(), RS_(L"Settings_ResetSettingsButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
Automation::AutomationProperties::SetHelpText(OpenJsonNavItem(), RS_(L"Nav_OpenJSON/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
@@ -180,6 +216,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WUX::Controls::ToolTipService::SetToolTip(OpenJsonNavItem(), box_value(RS_(L"Nav_OpenJSON/Content")));
_breadcrumbs = single_threaded_observable_vector<IInspectable>();
_UpdateSearchIndex();
extensionsVMImpl->LazyLoadExtensions();
}
// Method Description:
@@ -218,11 +256,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// Repopulate profile-related menu items
_InitializeProfilesList();
// Update the Nav State with the new version of the settings
_colorSchemesPageVM.UpdateSettings(_settingsClone);
_actionsVM.UpdateSettings(_settingsClone);
_newTabMenuPageVM.UpdateSettings(_settingsClone);
_extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM);
_profileDefaultsVM = nullptr; // Lazy-loaded upon navigation
// We'll update the profile in the _profilesNavState whenever we actually navigate to one
@@ -243,8 +283,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (stringTag == breadcrumbStringTag)
{
// found the one that was selected before the refresh
SettingsNav().SelectedItem(item);
_Navigate(*breadcrumbStringTag, crumb->SubPage());
SettingsNav().SelectedItem(item);
return;
}
}
@@ -254,8 +294,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// navigate to the NewTabMenu page,
// _Navigate() will handle trying to find the right subpage
SettingsNav().SelectedItem(item);
_Navigate(breadcrumbFolderEntry, BreadcrumbSubPage::NewTabMenu_Folder);
SettingsNav().SelectedItem(item);
return;
}
}
@@ -265,8 +305,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// navigate to the Extensions page,
// _Navigate() will handle trying to find the right subpage
SettingsNav().SelectedItem(item);
_Navigate(breadcrumbExtensionPackage, BreadcrumbSubPage::Extensions_Extension);
SettingsNav().SelectedItem(item);
return;
}
}
@@ -278,8 +318,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (profileTag->OriginalProfileGuid() == breadcrumbProfileTag->OriginalProfileGuid())
{
// found the one that was selected before the refresh
SettingsNav().SelectedItem(item);
_Navigate(*profileTag, crumb->SubPage());
SettingsNav().SelectedItem(item);
return;
}
}
@@ -293,8 +333,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// This happens when the selected item was a profile which doesn't exist in the new configuration
// We can use menuItemsSTL here because the only things they miss are profile entries.
const auto& firstItem{ _menuItemSource.GetAt(0).as<MUX::Controls::NavigationViewItem>() };
SettingsNav().SelectedItem(firstItem);
_Navigate(unbox_value<hstring>(firstItem.Tag()), BreadcrumbSubPage::None);
SettingsNav().SelectedItem(firstItem);
_UpdateSearchIndex();
}
void MainPage::SetHostingWindow(uint64_t hostingWindow) noexcept
@@ -455,31 +497,37 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto settingName{ args.PropertyName() };
if (settingName == L"CurrentPage")
{
// extract ElementToFocus and clear it; we only want to use it once
auto vmImpl = get_self<ProfileViewModel>(profile);
const auto elementToFocus = vmImpl->ElementToFocus();
vmImpl->ElementToFocus({});
const auto currentPage = profile.CurrentPage();
if (currentPage == ProfileSubPage::Base)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
_breadcrumbs.Clear();
const auto crumb = winrt::make<Breadcrumb>(breadcrumbTag, breadcrumbText, BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
}
else if (currentPage == ProfileSubPage::Appearance)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Appearance>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Appearance>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(breadcrumbTag, RS_(L"Profile_Appearance/Header"), BreadcrumbSubPage::Profile_Appearance);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
}
else if (currentPage == ProfileSubPage::Terminal)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Terminal>(), profile);
contentFrame().Navigate(xaml_typename<Editor::Profiles_Terminal>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(breadcrumbTag, RS_(L"Profile_Terminal/Header"), BreadcrumbSubPage::Profile_Terminal);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
}
else if (currentPage == ProfileSubPage::Advanced)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Advanced>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Advanced>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(breadcrumbTag, RS_(L"Profile_Advanced/Header"), BreadcrumbSubPage::Profile_Advanced);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
@@ -488,39 +536,44 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
});
}
void MainPage::_Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage)
void MainPage::_Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
if (clickedItemTag == launchTag)
{
contentFrame().Navigate(xaml_typename<Editor::Launch>(), winrt::make<LaunchViewModel>(_settingsClone));
contentFrame().Navigate(xaml_typename<Editor::Launch>(), winrt::make<NavigateToPageArgs>(winrt::make<LaunchViewModel>(_settingsClone), *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Launch/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(LaunchNavItem());
}
else if (clickedItemTag == interactionTag)
{
contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings()));
contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToPageArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings()), *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Interaction/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(InteractionNavItem());
}
else if (clickedItemTag == renderingTag)
{
contentFrame().Navigate(xaml_typename<Editor::Rendering>(), winrt::make<RenderingViewModel>(_settingsClone));
contentFrame().Navigate(xaml_typename<Editor::Rendering>(), winrt::make<NavigateToPageArgs>(winrt::make<RenderingViewModel>(_settingsClone), *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Rendering/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(RenderingNavItem());
}
else if (clickedItemTag == compatibilityTag)
{
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<CompatibilityViewModel>(_settingsClone));
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<NavigateToPageArgs>(winrt::make<CompatibilityViewModel>(_settingsClone), *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Compatibility/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(CompatibilityNavItem());
}
else if (clickedItemTag == actionsTag)
{
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
contentFrame().Navigate(xaml_typename<Editor::Actions>(), _actionsVM);
contentFrame().Navigate(xaml_typename<Editor::Actions>(), winrt::make<NavigateToPageArgs>(_actionsVM, *this, elementToFocus));
SettingsNav().SelectedItem(ActionsNavItem());
if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr)
{
@@ -538,10 +591,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
else
{
// Navigate to the NewTabMenu page
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
SettingsNav().SelectedItem(NewTabMenuNavItem());
}
else if (clickedItemTag == extensionsTag)
{
@@ -553,42 +607,52 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else
{
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToPageArgs>(_extensionsVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
SettingsNav().SelectedItem(ExtensionsNavItem());
}
else if (clickedItemTag == globalProfileTag)
{
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) };
profileVM.SetupAppearances(_colorSchemesPageVM.AllColorSchemes());
profileVM.IsBaseLayer(true);
// lazy load profile defaults VM
if (!_profileDefaultsVM)
{
_profileDefaultsVM = _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher());
_profileDefaultsVM.SetupAppearances(_colorSchemesPageVM.AllColorSchemes());
_profileDefaultsVM.IsBaseLayer(true);
}
_SetupProfileEventHandling(profileVM);
_SetupProfileEventHandling(_profileDefaultsVM);
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profileVM, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<NavigateToPageArgs>(_profileDefaultsVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_ProfileDefaults/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(BaseLayerMenuItem());
// If we were given a label, make sure we are on the correct sub-page
if (subPage == BreadcrumbSubPage::Profile_Appearance)
// Pass along the element to focus to the ProfileViewModel.
// This will work as a staging area before we navigate to the correct sub-page
auto profileVMImpl = get_self<ProfileViewModel>(_profileDefaultsVM);
profileVMImpl->ElementToFocus(elementToFocus);
// Set the profile's 'CurrentPage' to the correct one, if this requires further navigation, the
// event handler will do it
const ProfileSubPage profileSubPage = ProfileSubPageFromBreadcrumb(subPage);
const bool needsForcedRefresh = _profileDefaultsVM.CurrentPage() == profileSubPage;
_profileDefaultsVM.CurrentPage(profileSubPage);
if (needsForcedRefresh)
{
profileVM.CurrentPage(ProfileSubPage::Appearance);
}
else if (subPage == BreadcrumbSubPage::Profile_Terminal)
{
profileVM.CurrentPage(ProfileSubPage::Terminal);
}
else if (subPage == BreadcrumbSubPage::Profile_Advanced)
{
profileVM.CurrentPage(ProfileSubPage::Advanced);
// If we're already on the correct sub-page, the PropertyChanged event won't fire.
// However, we still need to pass along the ElementToFocus, so we need to force a refresh.
profileVMImpl->ForceRefreshCurrentPage();
}
}
else if (clickedItemTag == colorSchemesTag)
{
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_ColorSchemes/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
contentFrame().Navigate(xaml_typename<Editor::ColorSchemes>(), _colorSchemesPageVM);
contentFrame().Navigate(xaml_typename<Editor::ColorSchemes>(), winrt::make<NavigateToPageArgs>(_colorSchemesPageVM, *this, elementToFocus));
SettingsNav().SelectedItem(ColorSchemesNavItem());
if (subPage == BreadcrumbSubPage::ColorSchemes_Edit)
{
@@ -597,17 +661,39 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (clickedItemTag == globalAppearanceTag)
{
contentFrame().Navigate(xaml_typename<Editor::GlobalAppearance>(), winrt::make<GlobalAppearanceViewModel>(_settingsClone.GlobalSettings()));
contentFrame().Navigate(xaml_typename<Editor::GlobalAppearance>(), winrt::make<NavigateToPageArgs>(winrt::make<GlobalAppearanceViewModel>(_settingsClone.GlobalSettings()), *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Appearance/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(AppearanceNavItem());
}
else if (clickedItemTag == addProfileTag)
{
auto addProfileState{ winrt::make<AddProfilePageNavigationState>(_settingsClone) };
auto addProfileState{ winrt::make<AddProfilePageNavigationState>(_settingsClone, elementToFocus) };
addProfileState.AddNew({ get_weak(), &MainPage::_AddProfileHandler });
contentFrame().Navigate(xaml_typename<Editor::AddProfile>(), addProfileState);
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_AddNewProfile/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
// Find the "Add new profile" menu item and select it
// It's likely at the very end of the list, so we'll search backwards
for (auto i = _menuItemSource.Size() - 1; i > 0; --i)
{
const auto& item = _menuItemSource.GetAt(i);
if (const auto& menuItem{ item.try_as<MUX::Controls::NavigationViewItem>() })
{
if (const auto& tag{ menuItem.Tag() })
{
if (const auto& stringTag{ tag.try_as<hstring>() })
{
if (*stringTag == addProfileTag)
{
SettingsNav().SelectedItem(item);
break;
}
}
}
}
}
}
}
@@ -616,7 +702,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// - NOTE: this does not update the selected item.
// Arguments:
// - profile - the profile object we are getting a view of
void MainPage::_Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage)
void MainPage::_Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
@@ -624,44 +710,74 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (profile.Orphaned())
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base_Orphaned>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base_Orphaned>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(profile), profile.Name(), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
profile.CurrentPage(ProfileSubPage::Base);
return;
}
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<NavigateToPageArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(profile), profile.Name(), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
if (const auto profileNavItem = _FindProfileNavItem(profile.OriginalProfileGuid()))
{
SettingsNav().SelectedItem(profileNavItem);
}
// Pass along the element to focus to the ProfileViewModel.
// This will work as a staging area before we navigate to the correct sub-page
auto profileVMImpl = get_self<ProfileViewModel>(profile);
profileVMImpl->ElementToFocus(elementToFocus);
// Set the profile's 'CurrentPage' to the correct one, if this requires further navigation, the
// event handler will do it
if (subPage == BreadcrumbSubPage::None)
const ProfileSubPage profileSubPage = ProfileSubPageFromBreadcrumb(subPage);
const bool needsForcedRefresh = profile.CurrentPage() == profileSubPage;
profile.CurrentPage(profileSubPage);
if (needsForcedRefresh)
{
profile.CurrentPage(ProfileSubPage::Base);
}
else if (subPage == BreadcrumbSubPage::Profile_Appearance)
{
profile.CurrentPage(ProfileSubPage::Appearance);
}
else if (subPage == BreadcrumbSubPage::Profile_Terminal)
{
profile.CurrentPage(ProfileSubPage::Terminal);
}
else if (subPage == BreadcrumbSubPage::Profile_Advanced)
{
profile.CurrentPage(ProfileSubPage::Advanced);
// If we're already on the correct sub-page, the PropertyChanged event won't fire.
// However, we still need to pass along the ElementToFocus, so we need to force a refresh.
profileVMImpl->ForceRefreshCurrentPage();
}
}
void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage)
void MainPage::_Navigate(const Editor::ColorSchemeViewModel& colorSchemeVM, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
const auto crumb = winrt::make<Breadcrumb>(box_value(colorSchemesTag), RS_(L"Nav_ColorSchemes/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
contentFrame().Navigate(xaml_typename<Editor::ColorSchemes>(), winrt::make<NavigateToPageArgs>(_colorSchemesPageVM, *this, elementToFocus));
SettingsNav().SelectedItem(ColorSchemesNavItem());
// Pass along the element to focus to the ColorSchemesPageViewModel.
// This will work as a staging area before we navigate to EditColorScheme
get_self<ColorSchemesPageViewModel>(_colorSchemesPageVM)->ElementToFocus(elementToFocus);
// Set CurrentScheme BEFORE the CurrentPage!
// Doing so triggers the PropertyChanged event which performs the navigation to EditColorScheme
if (subPage == BreadcrumbSubPage::None)
{
_colorSchemesPageVM.CurrentScheme(nullptr);
_colorSchemesPageVM.CurrentPage(ColorSchemesSubPage::Base);
}
else
{
_colorSchemesPageVM.CurrentScheme(colorSchemeVM);
_colorSchemesPageVM.CurrentPage(ColorSchemesSubPage::EditColorScheme);
}
}
void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(NewTabMenuNavItem());
if (subPage == BreadcrumbSubPage::None)
{
@@ -688,13 +804,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage)
void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToPageArgs>(_extensionsVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(ExtensionsNavItem());
if (subPage == BreadcrumbSubPage::None)
{
@@ -708,9 +825,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (pkgVM.Package().Source() == extPkgVM.Package().Source())
{
// Take advantage of the PropertyChanged event to navigate
// to the correct extension package and build the breadcrumbs as we go
// to the correct extension package and build the breadcrumbs as we go.
const auto wasAlreadyOnExtension = (_extensionsVM.CurrentExtensionPackage() == pkgVM);
_extensionsVM.CurrentExtensionPackage(pkgVM);
found = true;
// If CurrentExtensionPackage was already this extension, PropertyChanged won't fire,
// so we add the breadcrumb manually.
if (wasAlreadyOnExtension)
{
const auto extCrumb = winrt::make<Breadcrumb>(box_value(pkgVM), pkgVM.DisplayName(), BreadcrumbSubPage::Extensions_Extension);
_breadcrumbs.Append(extCrumb);
}
break;
}
}
@@ -722,6 +848,38 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_Navigate(const Editor::CommandViewModel& commandVM, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::Actions>(), winrt::make<NavigateToPageArgs>(_actionsVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(actionsTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
SettingsNav().SelectedItem(ActionsNavItem());
if (subPage == BreadcrumbSubPage::None || !commandVM)
{
_actionsVM.CurrentCommand(nullptr);
}
else
{
// Take advantage of the PropertyChanged event to navigate
// to EditAction and build the breadcrumbs as we go.
const auto wasAlreadyEdit = (_actionsVM.CurrentPage() == ActionsSubPage::Edit);
_actionsVM.CurrentCommand(commandVM);
_actionsVM.CurrentPage(ActionsSubPage::Edit);
// If CurrentPage was already Edit, PropertyChanged won't fire,
// so we navigate and add breadcrumb manually.
if (wasAlreadyEdit)
{
contentFrame().Navigate(xaml_typename<Editor::EditAction>(), winrt::make<implementation::NavigateToCommandArgs>(commandVM, *this));
const auto editCrumb = winrt::make<Breadcrumb>(box_value(actionsTag), RS_(L"Nav_EditAction/Content"), BreadcrumbSubPage::Actions_Edit);
_breadcrumbs.Append(editCrumb);
}
}
}
void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/)
{
_settingsClone.LogSettingChanges(false);
@@ -774,10 +932,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_MoveXamlParsedNavItemsIntoItemSource();
}
// Manually create a NavigationViewItem for each profile
// Manually create a NavigationViewItem and view model for each profile
// and keep a reference to them in a map so that we
// can easily modify the correct one when the associated
// profile changes.
_profileVMs.Clear();
for (const auto& profile : _settingsClone.AllProfiles())
{
if (!profile.Deleted())
@@ -798,7 +957,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
FontIcon icon;
// This is the "Add" symbol
icon.Glyph(L"\xE710");
icon.Glyph(NavTagIconMap[addProfileTag]);
addProfileItem.Icon(icon);
_menuItemSource.Append(addProfileItem);
@@ -850,8 +1009,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
// Select and navigate to the new profile
SettingsNav().SelectedItem(navItem);
_Navigate(profileViewModel, BreadcrumbSubPage::None);
SettingsNav().SelectedItem(navItem);
}
static MUX::Controls::InfoBadge _createGlyphIconBadge(wil::zwstring_view glyph)
@@ -907,6 +1066,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// Add an event handler for when the user wants to delete a profile.
profile.DeleteProfileRequested({ this, &MainPage::_DeleteProfile });
// Register the VM so that it appears in the search index
_profileVMs.Append(profile);
return profileNavItem;
}
@@ -932,9 +1094,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_menuItemSource.IndexOf(selectedItem, index);
_menuItemSource.RemoveAt(index);
// Remove it from the list of VMs
auto profileVM = selectedItem.as<MUX::Controls::NavigationViewItem>().Tag().as<Editor::ProfileViewModel>();
uint32_t vmIndex;
if (_menuItemSource.IndexOf(profileVM, vmIndex))
{
_profileVMs.RemoveAt(vmIndex);
}
// navigate to the profile next to this one
const auto newSelectedItem{ _menuItemSource.GetAt(index < _menuItemSource.Size() - 1 ? index : index - 1) };
SettingsNav().SelectedItem(newSelectedItem);
const auto newTag = newSelectedItem.as<MUX::Controls::NavigationViewItem>().Tag();
if (const auto profileViewModel = newTag.try_as<ProfileViewModel>())
{
@@ -947,6 +1116,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
// Since we are navigating to a new profile after deletion, scroll up to the top
SettingsMainPage_ScrollViewer().ChangeView(nullptr, 0.0, nullptr);
SettingsNav().SelectedItem(newSelectedItem);
}
}
@@ -956,6 +1126,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
void MainPage::_NavigateToProfileHandler(const IInspectable& /*sender*/, winrt::guid profileGuid)
{
if (const auto profileNavItem = _FindProfileNavItem(profileGuid))
{
_Navigate(profileNavItem.Tag().as<Editor::ProfileViewModel>(), BreadcrumbSubPage::None);
}
// Silently fail if the profile wasn't found
}
MUX::Controls::NavigationViewItem MainPage::_FindProfileNavItem(winrt::guid profileGuid) const
{
for (auto&& menuItem : _menuItemSource)
{
@@ -967,21 +1146,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (profileTag->OriginalProfileGuid() == profileGuid)
{
SettingsNav().SelectedItem(menuItem);
_Navigate(*profileTag, BreadcrumbSubPage::None);
return;
return navViewItem;
}
}
}
}
}
// Silently fail if the profile wasn't found
return nullptr;
}
void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/)
{
SettingsNav().SelectedItem(ColorSchemesNavItem());
_Navigate(hstring{ colorSchemesTag }, BreadcrumbSubPage::ColorSchemes_Edit);
SettingsNav().SelectedItem(ColorSchemesNavItem());
}
winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush()
@@ -1045,4 +1222,121 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
safe_void_coroutine MainPage::SettingsSearchBox_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& args)
{
if (args.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
{
// Only respond to user input, not programmatic text changes
co_return;
}
// remove leading spaces
std::wstring queryW{ sender.Text() };
const auto firstNonSpace{ queryW.find_first_not_of(L' ') };
if (firstNonSpace == std::wstring::npos)
{
// only spaces
const auto& searchBox = SettingsSearchBox();
searchBox.ItemsSource(nullptr);
searchBox.IsSuggestionListOpen(false);
co_return;
}
const hstring sanitizedQuery{ queryW.substr(firstNonSpace) };
if (sanitizedQuery.empty())
{
// empty query
const auto& searchBox = SettingsSearchBox();
searchBox.ItemsSource(nullptr);
searchBox.IsSuggestionListOpen(false);
co_return;
}
if (_currentSearch)
{
// a newer search has started, abandon this one
_currentSearch.Cancel();
co_return;
}
_currentSearch = SearchIndex::Instance().SearchAsync(sanitizedQuery,
_profileVMs.GetView(),
get_self<implementation::NewTabMenuViewModel>(_newTabMenuPageVM)->FolderTreeFlatList().GetView(),
_colorSchemesPageVM.AllColorSchemes().GetView(),
_extensionsVM.ExtensionPackages().GetView(),
_actionsVM.CommandList().GetView());
const auto results = co_await _currentSearch;
_currentSearch = nullptr;
// Update the UI with the results
const auto& searchBox = SettingsSearchBox();
searchBox.ItemsSource(results);
searchBox.IsSuggestionListOpen(true);
}
void MainPage::SettingsSearchBox_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& args)
{
if (args.ChosenSuggestion())
{
const auto& chosenResult{ args.ChosenSuggestion().as<FilteredSearchResult>() };
if (chosenResult->IsNoResultsPlaceholder())
{
// don't navigate anywhere
return;
}
// Navigate to the target page
const auto& indexEntry{ chosenResult->SearchIndexEntry() };
const auto& navigationArg{ chosenResult->NavigationArg() };
const auto& subpage{ indexEntry.Entry->SubPage };
const auto& elementToFocus{ indexEntry.Entry->ElementName };
if (const auto navArgString = navigationArg.try_as<hstring>())
{
_Navigate(*navArgString, subpage, elementToFocus);
}
else if (const auto& profileVM = navigationArg.try_as<Editor::ProfileViewModel>())
{
_Navigate(profileVM, subpage, elementToFocus);
}
else if (const auto& colorSchemeVM = navigationArg.try_as<Editor::ColorSchemeViewModel>())
{
_Navigate(colorSchemeVM, subpage, elementToFocus);
}
else if (const auto& ntmEntryVM = navigationArg.try_as<Editor::NewTabMenuEntryViewModel>())
{
_Navigate(ntmEntryVM, subpage, elementToFocus);
}
else if (const auto& extPkgVM = navigationArg.try_as<Editor::ExtensionPackageViewModel>())
{
_Navigate(extPkgVM, subpage, elementToFocus);
}
else if (const auto& commandVM = navigationArg.try_as<Editor::CommandViewModel>())
{
_Navigate(commandVM, subpage, elementToFocus);
}
SettingsSearchBox().Text(L"");
}
}
void MainPage::SettingsSearchBox_SuggestionChosen(const AutoSuggestBox&, const AutoSuggestBoxSuggestionChosenEventArgs&)
{
// Don't navigate on arrow keys
// Handle Enter/Click with QuerySubmitted() to instead
// AutoSuggestBox will pass the chosen item to QuerySubmitted() via args.ChosenSuggestion()
}
safe_void_coroutine MainPage::_UpdateSearchIndex()
{
auto weakThis = get_weak();
co_await winrt::resume_background();
if (auto strongThis = weakThis.get())
{
if (strongThis->_currentSearch && strongThis->_currentSearch.Status() == AsyncStatus::Started)
{
strongThis->_currentSearch.Cancel();
}
SearchIndex::Instance().Reset();
}
}
}

View File

@@ -5,7 +5,9 @@
#include "MainPage.g.h"
#include "Breadcrumb.g.h"
#include "NavigateToPageArgs.g.h"
#include "Utils.h"
#include "SearchIndex.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@@ -23,6 +25,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(BreadcrumbSubPage, SubPage);
};
struct NavigateToPageArgs : NavigateToPageArgsT<NavigateToPageArgs>
{
public:
NavigateToPageArgs(Windows::Foundation::IInspectable viewModel, Editor::IHostedInWindow windowRoot, const hstring& elementToFocus = {}) :
_ViewModel(viewModel),
_WeakWindowRoot(windowRoot),
_ElementToFocus(elementToFocus) {}
Editor::IHostedInWindow WindowRoot() const noexcept { return _WeakWindowRoot.get(); }
Windows::Foundation::IInspectable ViewModel() const noexcept { return _ViewModel; }
hstring ElementToFocus() const noexcept { return _ElementToFocus; }
private:
winrt::weak_ref<Editor::IHostedInWindow> _WeakWindowRoot;
Windows::Foundation::IInspectable _ViewModel{ nullptr };
hstring _ElementToFocus{};
};
struct MainPage : MainPageT<MainPage>
{
MainPage() = delete;
@@ -30,6 +50,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void UpdateSettings(const Model::CascadiaSettings& settings);
safe_void_coroutine SettingsSearchBox_TextChanged(const Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& args);
void SettingsSearchBox_QuerySubmitted(const Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& args);
void SettingsSearchBox_SuggestionChosen(const Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxSuggestionChosenEventArgs& args);
void SettingsNav_Loaded(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
void SettingsNav_ItemInvoked(const Microsoft::UI::Xaml::Controls::NavigationView& sender, const Microsoft::UI::Xaml::Controls::NavigationViewItemInvokedEventArgs& args);
void SaveButton_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
@@ -68,21 +92,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _SetupProfileEventHandling(const winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel profile);
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 _Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage);
void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::ColorSchemeViewModel& colorSchemeVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::CommandViewModel& commandVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid);
void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args);
Microsoft::UI::Xaml::Controls::NavigationViewItem _FindProfileNavItem(winrt::guid profileGuid) const;
void _UpdateBackgroundForMica();
void _MoveXamlParsedNavItemsIntoItemSource();
safe_void_coroutine _UpdateSearchIndex();
winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel _profileDefaultsVM{ nullptr };
Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel> _profileVMs{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::ActionsViewModel _actionsVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr };
Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable>> _currentSearch{ nullptr };
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _actionsViewModelChangedRevoker;

View File

@@ -16,6 +16,13 @@ namespace Microsoft.Terminal.Settings.Editor
UInt64 GetHostingWindow();
}
runtimeclass NavigateToPageArgs
{
IHostedInWindow WindowRoot { get; };
IInspectable ViewModel { get; };
String ElementToFocus { get; };
}
enum BreadcrumbSubPage
{
None = 0,

View File

@@ -52,6 +52,75 @@
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="BasicSearchResultTemplate"
x:DataType="local:FilteredSearchResult">
<Grid HorizontalAlignment="Stretch"
ColumnSpacing="8"
ToolTipService.ToolTip="{x:Bind Label}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{x:Bind Icon}" />
<TextBlock Grid.Column="1"
Text="{x:Bind Label}"
TextTrimming="CharacterEllipsis" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="ComplexSearchResultTemplate"
x:DataType="local:FilteredSearchResult">
<Grid HorizontalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{x:Bind Icon}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
FontSize="{ThemeResource BodyTextBlockFontSize}"
Foreground="{ThemeResource TextFillColorPrimary}"
Text="{x:Bind Label}"
TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Row="1"
Grid.Column="1"
FontSize="{ThemeResource CaptionTextBlockFontSize}"
Foreground="{ThemeResource TextFillColorSecondary}"
Text="{x:Bind SecondaryLabel}"
TextTrimming="CharacterEllipsis" />
<ToolTipService.ToolTip>
<StackPanel>
<TextBlock FontSize="{ThemeResource CaptionTextBlockFontSize}"
Foreground="{ThemeResource TextFillColorPrimary}"
Text="{x:Bind Label}" />
<TextBlock FontSize="{ThemeResource CaptionTextBlockFontSize}"
Foreground="{ThemeResource TextFillColorSecondary}"
Text="{x:Bind SecondaryLabel}" />
</StackPanel>
</ToolTipService.ToolTip>
</Grid>
</DataTemplate>
<local:SearchResultTemplateSelector x:Key="SearchResultTemplateSelector"
BasicTemplate="{StaticResource BasicSearchResultTemplate}"
ComplexTemplate="{StaticResource ComplexSearchResultTemplate}" />
<SolidColorBrush x:Key="NavigationViewExpandedPaneBackground"
Color="Transparent" />
<SolidColorBrush x:Key="NavigationViewContentBackground"
@@ -99,62 +168,45 @@
</Grid>
</muxc:NavigationView.Header>
<muxc:NavigationView.MenuItems>
<muxc:NavigationView.AutoSuggestBox>
<AutoSuggestBox x:Name="SettingsSearchBox"
x:Uid="Nav_SearchBox"
ItemTemplateSelector="{StaticResource SearchResultTemplateSelector}"
QueryIcon="Find"
QuerySubmitted="SettingsSearchBox_QuerySubmitted"
SuggestionChosen="SettingsSearchBox_SuggestionChosen"
TextChanged="SettingsSearchBox_TextChanged" />
</muxc:NavigationView.AutoSuggestBox>
<muxc:NavigationView.MenuItems>
<muxc:NavigationViewItem x:Name="LaunchNavItem"
x:Uid="Nav_Launch"
Tag="Launch_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE7B5;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="Launch_Nav" />
<muxc:NavigationViewItem x:Name="InteractionNavItem"
x:Uid="Nav_Interaction"
Tag="Interaction_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE7C9;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="Interaction_Nav" />
<muxc:NavigationViewItem x:Name="AppearanceNavItem"
x:Uid="Nav_Appearance"
Tag="GlobalAppearance_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE771;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="GlobalAppearance_Nav" />
<muxc:NavigationViewItem x:Name="ColorSchemesNavItem"
x:Uid="Nav_ColorSchemes"
Tag="ColorSchemes_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE790;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="ColorSchemes_Nav" />
<muxc:NavigationViewItem x:Name="RenderingNavItem"
x:Uid="Nav_Rendering"
Tag="Rendering_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE7F8;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="Rendering_Nav" />
<muxc:NavigationViewItem x:Name="CompatibilityNavItem"
x:Uid="Nav_Compatibility"
Tag="Compatibility_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEC7A;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="Compatibility_Nav" />
<muxc:NavigationViewItem x:Name="ActionsNavItem"
x:Uid="Nav_Actions"
Tag="Actions_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE765;" />
</muxc:NavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge Style="{StaticResource NewInfoBadge}"
Visibility="{x:Bind ActionsVM.DisplayBadge, Mode=OneWay}" />
@@ -163,42 +215,24 @@
<muxc:NavigationViewItem x:Name="NewTabMenuNavItem"
x:Uid="Nav_NewTabMenu"
Tag="NewTabMenu_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE71d;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="NewTabMenu_Nav" />
<muxc:NavigationViewItem x:Name="ExtensionsNavItem"
x:Uid="Nav_Extensions"
Tag="Extensions_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEA86;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="Extensions_Nav" />
<muxc:NavigationViewItemHeader x:Uid="Nav_Profiles" />
<muxc:NavigationViewItem x:Name="BaseLayerMenuItem"
x:Uid="Nav_ProfileDefaults"
Tag="GlobalProfile_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE81E;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="GlobalProfile_Nav" />
</muxc:NavigationView.MenuItems>
<muxc:NavigationView.FooterMenuItems>
<!-- The OpenJson item needs both Tapped and KeyDown handler -->
<muxc:NavigationViewItem x:Name="OpenJsonNavItem"
x:Uid="Nav_OpenJSON"
SelectsOnInvoked="False"
Tag="OpenJson_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE713;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
Tag="OpenJson_Nav" />
</muxc:NavigationView.FooterMenuItems>
<Grid>

View File

@@ -69,6 +69,10 @@
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="IconPicker.h">
<DependentUpon>IconPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="EditColorScheme.h">
<DependentUpon>EditColorScheme.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -89,6 +93,8 @@
<DependentUpon>NewTabMenu.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="pch.h" />
<ClInclude Include="SearchIndex.h" />
<ClInclude Include="NavConstants.h" />
<ClInclude Include="MainPage.h">
<DependentUpon>MainPage.xaml</DependentUpon>
</ClInclude>
@@ -169,6 +175,8 @@
</ClInclude>
<ClInclude Include="Utils.h" />
<ClInclude Include="PreviewConnection.h" />
<ClInclude Include="$(GeneratedFilesDir)GeneratedSettingsIndex.g.h" />
<ClInclude Include="../fzf/fzf.h" />
</ItemGroup>
<!-- ========================= XAML files ======================== -->
<ItemGroup>
@@ -193,6 +201,9 @@
<Page Include="NullableColorPicker.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="IconPicker.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="EditColorScheme.xaml">
<SubType>Designer</SubType>
</Page>
@@ -269,6 +280,10 @@
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="IconPicker.cpp">
<DependentUpon>IconPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="EditColorScheme.cpp">
<DependentUpon>EditColorScheme.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -291,6 +306,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SearchIndex.cpp" />
<ClCompile Include="MainPage.cpp">
<DependentUpon>MainPage.xaml</DependentUpon>
</ClCompile>
@@ -373,6 +389,8 @@
<ClCompile Include="PreviewConnection.cpp">
<DependentUpon>PreviewConnection.h</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)GeneratedSettingsIndex.g.cpp" />
<ClCompile Include="../fzf/fzf.cpp" />
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
@@ -397,6 +415,10 @@
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="IconPicker.idl">
<DependentUpon>IconPicker.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="EditColorScheme.idl">
<DependentUpon>EditColorScheme.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -425,6 +447,7 @@
<DependentUpon>Rendering.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="SearchIndex.idl" />
<Midl Include="MainPage.idl">
<DependentUpon>MainPage.xaml</DependentUpon>
</Midl>
@@ -541,4 +564,10 @@
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
<Target Name="GenerateSettingsIndex"
Inputs="@(Page);$(OpenConsoleDir)tools\GenerateSettingsIndex.ps1"
Outputs="$(GeneratedFilesDir)GeneratedSettingsIndex.g.h;$(GeneratedFilesDir)GeneratedSettingsIndex.g.cpp"
BeforeTargets="ClCompile">
<Exec Command="pwsh.exe -NoProfile -ExecutionPolicy Unrestricted &quot;$(OpenConsoleDir)tools\GenerateSettingsIndex.ps1&quot; -SourceDir &quot;$(MSBuildThisFileDirectory).&quot; -OutputDir &quot;$(MSBuildThisFileDirectory)$(GeneratedFilesDir).&quot;" />
</Target>
</Project>

View File

@@ -55,6 +55,7 @@
<Page Include="AddProfile.xaml" />
<Page Include="KeyChordListener.xaml" />
<Page Include="NullableColorPicker.xaml" />
<Page Include="IconPicker.xaml" />
<Page Include="NewTabMenu.xaml" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <til/static_map.h>
// Navigation tags used to identify pages in the Settings UI NavigationView.
// These tags are stored as the Tag property on NavigationViewItems.
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
inline constexpr std::wstring_view openJsonTag{ L"OpenJson_Nav" };
inline constexpr std::wstring_view launchTag{ L"Launch_Nav" };
inline constexpr std::wstring_view interactionTag{ L"Interaction_Nav" };
inline constexpr std::wstring_view renderingTag{ L"Rendering_Nav" };
inline constexpr std::wstring_view compatibilityTag{ L"Compatibility_Nav" };
inline constexpr std::wstring_view actionsTag{ L"Actions_Nav" };
inline constexpr std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" };
inline constexpr std::wstring_view extensionsTag{ L"Extensions_Nav" };
inline constexpr std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" };
inline constexpr std::wstring_view addProfileTag{ L"AddProfile" };
inline constexpr std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" };
inline constexpr std::wstring_view globalAppearanceTag{ L"GlobalAppearance_Nav" };
// Map from navigation tags to Segoe MDL2 Assets icon glyphs
inline constexpr til::static_map NavTagIconMap{
std::pair{ launchTag, L"\xE7B5" }, /* Set Lock Screen */
std::pair{ interactionTag, L"\xE7C9" }, /* Touch Pointer */
std::pair{ globalAppearanceTag, L"\xE771" }, /* Personalize */
std::pair{ colorSchemesTag, L"\xE790" }, /* Color */
std::pair{ renderingTag, L"\xE7F8" }, /* Device Laptop No Pic */
std::pair{ compatibilityTag, L"\xEC7A" }, /* Developer Tools */
std::pair{ actionsTag, L"\xE765" }, /* Keyboard Classic */
std::pair{ newTabMenuTag, L"\xE71D" }, /* All Apps */
std::pair{ extensionsTag, L"\xEA86" }, /* Puzzle */
std::pair{ globalProfileTag, L"\xE81E" }, /* Map Layers */
std::pair{ addProfileTag, L"\xE710" }, /* Add */
std::pair{ openJsonTag, L"\xE713" }, /* Settings */
};
}

View File

@@ -3,7 +3,9 @@
#include "pch.h"
#include "NewTabMenu.h"
#include "NavigateToPageArgs.g.h"
#include "NewTabMenu.g.cpp"
#include "NavigateToPageArgs.g.h"
#include "NewTabMenuEntryTemplateSelector.g.cpp"
#include "EnumEntry.h"
@@ -41,7 +43,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void NewTabMenu::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::NewTabMenuViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::NewTabMenuViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
_windowRoot = args.WindowRoot();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -38,11 +38,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AddFolderNameTextBox_KeyDown(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void AddFolderNameTextBox_TextChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs& e);
Editor::IHostedInWindow WindowRoot() const noexcept { return _windowRoot; }
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
private:
Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr };
Editor::IHostedInWindow _windowRoot;
void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry);
};

View File

@@ -9,6 +9,7 @@ namespace Microsoft.Terminal.Settings.Editor
{
NewTabMenu();
NewTabMenuViewModel ViewModel { get; };
IHostedInWindow WindowRoot { get; };
}
[default_interface] runtimeclass NewTabMenuEntryTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector

View File

@@ -319,29 +319,51 @@
Visibility="{x:Bind ViewModel.IsFolderView, Mode=OneWay}">
<TextBlock x:Uid="NewTabMenu_CurrentFolderTextBlock"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- TODO GH #18281: Icon -->
<!-- Once PR #17965 merges, we can add that kind of control to set an icon -->
<!-- Name -->
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderName"
Grid.Row="0"
<local:SettingContainer x:Name="CurrentFolderName"
x:Uid="NewTabMenu_CurrentFolderName"
CurrentValue="{x:Bind ViewModel.CurrentFolderName, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<TextBox Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind ViewModel.CurrentFolderName, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Icon -->
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderIcon"
CurrentValueAccessibleName="{x:Bind ViewModel.CurrentFolderLocalizedIcon, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
<local:SettingContainer.CurrentValue>
<Grid>
<ContentControl Width="16"
Height="16"
Content="{x:Bind ViewModel.CurrentFolderIconPreview, Mode=OneWay}"
IsTabStop="False"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.CurrentFolderUsingNoIcon), Mode=OneWay}" />
<TextBlock Margin="0,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{x:Bind ViewModel.CurrentFolderLocalizedIcon, Mode=OneWay}"
Visibility="{x:Bind ViewModel.CurrentFolderUsingNoIcon, Mode=OneWay}" />
</Grid>
</local:SettingContainer.CurrentValue>
<local:SettingContainer.Content>
<local:IconPicker CurrentIconPath="{x:Bind ViewModel.CurrentFolderIconPath, Mode=TwoWay}"
WindowRoot="{x:Bind WindowRoot, Mode=OneWay}" />
</local:SettingContainer.Content>
</local:SettingContainer>
<!-- Inlining -->
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderInlining"
Grid.Row="1">
<local:SettingContainer x:Name="CurrentFolderInlining"
x:Uid="NewTabMenu_CurrentFolderInlining">
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderInlining, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Allow Empty -->
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderAllowEmpty"
Grid.Row="2">
<local:SettingContainer x:Name="CurrentFolderAllowEmpty"
x:Uid="NewTabMenu_CurrentFolderAllowEmpty">
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderAllowEmpty, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
@@ -353,7 +375,8 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Add Profile -->
<local:SettingContainer x:Uid="NewTabMenu_AddProfile"
<local:SettingContainer x:Name="AddProfile"
x:Uid="NewTabMenu_AddProfile"
FontIconGlyph="&#xE756;"
Style="{StaticResource SettingContainerWithIcon}">
@@ -402,7 +425,8 @@
</local:SettingContainer>
<!-- Add Separator -->
<local:SettingContainer x:Uid="NewTabMenu_AddSeparator"
<local:SettingContainer x:Name="AddSeparator"
x:Uid="NewTabMenu_AddSeparator"
FontIconGlyph="&#xE76f;"
Style="{StaticResource SettingContainerWithIcon}">
<Button x:Name="AddSeparatorButton"
@@ -418,7 +442,8 @@
</local:SettingContainer>
<!-- Add Folder -->
<local:SettingContainer x:Uid="NewTabMenu_AddFolder"
<local:SettingContainer x:Name="AddFolder"
x:Uid="NewTabMenu_AddFolder"
FontIconGlyph="&#xF12B;"
Style="{StaticResource SettingContainerWithIcon}">
<StackPanel Orientation="Horizontal"
@@ -444,7 +469,8 @@
</local:SettingContainer>
<!-- Add Match Profiles -->
<local:SettingContainer x:Uid="NewTabMenu_AddMatchProfiles"
<local:SettingContainer x:Name="AddMatchProfiles"
x:Uid="NewTabMenu_AddMatchProfiles"
FontIconGlyph="&#xE748;"
Style="{StaticResource ExpanderSettingContainerStyleWithIcon}">
<StackPanel Spacing="8">

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "NewTabMenuViewModel.h"
#include "IconPicker.h"
#include "NewTabMenuViewModel.g.cpp"
#include "FolderTreeViewEntry.g.cpp"
@@ -160,6 +161,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// FolderTree needs to be updated when a folder is renamed
_folderTreeCache = nullptr;
}
else if (viewModelProperty == L"Icon")
{
_NotifyChanges(L"CurrentFolderIconPreview", L"CurrentFolderLocalizedIcon", L"CurrentFolderIconPath", L"CurrentFolderUsingNoIcon");
}
}
hstring NewTabMenuViewModel::CurrentFolderName() const
@@ -216,6 +221,60 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
Windows::UI::Xaml::Controls::IconElement NewTabMenuViewModel::CurrentFolderIconPreview() const
{
if (!_CurrentFolder)
{
return nullptr;
}
// IconWUX sets the icon width/height to 32 by default
auto icon = Microsoft::Terminal::UI::IconPathConverter::IconWUX(_CurrentFolder.Icon());
icon.Width(16);
icon.Height(16);
return icon;
}
winrt::hstring NewTabMenuViewModel::CurrentFolderLocalizedIcon() const
{
if (!_CurrentFolder)
{
return {};
}
if (CurrentFolderUsingNoIcon())
{
return RS_(L"IconPicker_IconTypeNone");
}
return _CurrentFolder.Icon(); // For display as a string
}
winrt::hstring NewTabMenuViewModel::CurrentFolderIconPath() const
{
if (!_CurrentFolder)
{
return {};
}
return _CurrentFolder.Icon();
}
void NewTabMenuViewModel::CurrentFolderIconPath(const winrt::hstring& path)
{
if (_CurrentFolder && _CurrentFolder.Icon() != path)
{
_CurrentFolder.Icon(path);
_NotifyChanges(L"CurrentFolderIconPreview", L"CurrentFolderLocalizedIcon", L"CurrentFolderIconPath", L"CurrentFolderUsingNoIcon");
}
}
bool NewTabMenuViewModel::CurrentFolderUsingNoIcon() const noexcept
{
if (!_CurrentFolder)
{
return false;
}
const auto icon{ _CurrentFolder.Icon() };
return icon.empty() || icon == IconPicker::HideIconValue;
}
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> NewTabMenuViewModel::CurrentView() const
{
if (!_CurrentFolder)
@@ -451,6 +510,25 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _folderTreeCache;
}
Collections::IObservableVector<Editor::FolderEntryViewModel> NewTabMenuViewModel::FolderTreeFlatList() const
{
std::vector<Editor::FolderEntryViewModel> flatList;
_FolderTreeFlatListImpl(_rootEntries, flatList);
return single_threaded_observable_vector<Editor::FolderEntryViewModel>(std::move(flatList));
}
void NewTabMenuViewModel::_FolderTreeFlatListImpl(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entriesToAdd, std::vector<Editor::FolderEntryViewModel>& flatList)
{
for (const auto& entry : entriesToAdd)
{
if (const auto& folderVM = entry.try_as<Editor::FolderEntryViewModel>())
{
flatList.push_back(folderVM);
_FolderTreeFlatListImpl(folderVM.Entries(), flatList);
}
}
}
// This recursively constructs the FolderTree
FolderTreeViewEntry::FolderTreeViewEntry(Editor::FolderEntryViewModel folderEntry) :
_folderEntry{ folderEntry },

View File

@@ -47,8 +47,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool CurrentFolderAllowEmpty() const;
void CurrentFolderAllowEmpty(bool value);
Windows::UI::Xaml::Controls::IconElement CurrentFolderIconPreview() const;
winrt::hstring CurrentFolderLocalizedIcon() const;
winrt::hstring CurrentFolderIconPath() const;
void CurrentFolderIconPath(const winrt::hstring& path);
bool CurrentFolderUsingNoIcon() const noexcept;
Windows::Foundation::Collections::IObservableVector<Model::Profile> AvailableProfiles() const { return _Settings.AllProfiles(); }
Windows::Foundation::Collections::IObservableVector<Editor::FolderTreeViewEntry> FolderTree() const;
Windows::Foundation::Collections::IObservableVector<Editor::FolderEntryViewModel> FolderTreeFlatList() const;
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> CurrentView() const;
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderEntryViewModel, CurrentFolder, nullptr);
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderTreeViewEntry, CurrentFolderTreeViewSelectedItem, nullptr);
@@ -67,6 +74,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel>::VectorChanged_revoker _rootEntriesChangedRevoker;
static bool _IsRemainingProfilesEntryMissing(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entries);
static void _FolderTreeFlatListImpl(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entriesToAdd, std::vector<Editor::FolderEntryViewModel>& flatList);
void _FolderPropertyChanged(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
};
@@ -134,6 +142,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Inlining(bool value);
hstring Icon() const { return _FolderEntry.Icon().Path(); }
void Icon(const hstring& value)
{
_FolderEntry.Icon(Model::MediaResourceHelper::FromString(value));
_NotifyChanges(L"Icon");
}
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Name);
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, AllowEmpty);

View File

@@ -41,6 +41,11 @@ namespace Microsoft.Terminal.Settings.Editor
String ProfileMatcherCommandline;
String AddFolderName;
Windows.UI.Xaml.Controls.IconElement CurrentFolderIconPreview { get; };
String CurrentFolderLocalizedIcon { get; };
String CurrentFolderIconPath;
Boolean CurrentFolderUsingNoIcon { get; };
void RequestReorderEntry(NewTabMenuEntryViewModel vm, Boolean goingUp);
void RequestDeleteEntry(NewTabMenuEntryViewModel vm);
void RequestMoveEntriesToFolder(IVector<NewTabMenuEntryViewModel> entries, FolderEntryViewModel folderEntry);
@@ -83,7 +88,7 @@ namespace Microsoft.Terminal.Settings.Editor
FolderEntryViewModel(Microsoft.Terminal.Settings.Model.FolderEntry folderEntry, Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
String Name;
String Icon { get; };
String Icon;
Boolean Inlining;
Boolean AllowEmpty;
IObservableVector<Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel> Entries;

View File

@@ -4,14 +4,13 @@
#include "pch.h"
#include "ProfileViewModel.h"
#include "ProfileViewModel.g.cpp"
#include "EnumEntry.h"
#include "Appearances.h"
#include "EnumEntry.h"
#include "IconPicker.h"
#include "../WinRTUtils/inc/Utils.h"
#include "../../renderer/base/FontCache.h"
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
#include "SegoeFluentIconList.h"
#include "../../types/inc/utils.hpp"
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Xaml;
@@ -28,9 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_FontList{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> ProfileViewModel::_BuiltInIcons{ nullptr };
static constexpr std::wstring_view HideIconValue{ L"none" };
ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings, const Windows::UI::Core::CoreDispatcher& dispatcher) :
_profile{ profile },
@@ -47,17 +43,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_InitializeCurrentBellSounds();
// set up IconTypes
std::vector<IInspectable> iconTypes;
iconTypes.reserve(4);
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeNone"), box_value(IconType::None)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeFontIcon"), box_value(IconType::FontIcon)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeEmoji"), box_value(IconType::Emoji)));
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeImage"), box_value(IconType::Image)));
_IconTypes = winrt::single_threaded_vector<IInspectable>(std::move(iconTypes));
_DeduceCurrentIconType();
_DeduceCurrentBuiltInIcon();
// Add a property changed handler to our own property changed event.
// This propagates changes from the settings model to anybody listening to our
// unique view model members.
@@ -92,32 +77,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (viewModelProperty == L"Icon")
{
// _DeduceCurrentIconType() ends with a "CurrentIconType" notification
// so we don't need to call _UpdateIconPreview() here
_DeduceCurrentIconType();
// The icon changed; let's re-evaluate it with its new context.
_appSettings.ResolveMediaResources();
}
else if (viewModelProperty == L"CurrentIconType")
{
// "Using*" handles the visibility of the IconType-related UI.
// The others propagate the rendered icon into a preview (i.e. nav view, container item)
_NotifyChanges(L"UsingNoIcon",
L"UsingBuiltInIcon",
L"UsingEmojiIcon",
L"UsingImageIcon",
L"LocalizedIcon",
// Propagate the rendered icon into a preview (i.e. nav view, container item)
_NotifyChanges(L"LocalizedIcon",
L"IconPreview",
L"IconPath",
L"EvaluatedIcon");
}
else if (viewModelProperty == L"CurrentBuiltInIcon")
{
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
}
else if (viewModelProperty == L"CurrentEmojiIcon")
{
IconPath(CurrentEmojiIcon());
L"EvaluatedIcon",
L"UsingNoIcon");
}
else if (viewModelProperty == L"CurrentBellSounds")
{
@@ -190,61 +158,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_defaultAppearanceViewModel.IsDefault(true);
}
void ProfileViewModel::_UpdateBuiltInIcons()
{
std::vector<Editor::EnumEntry> builtInIcons;
for (auto& [val, name] : s_SegoeFluentIcons)
{
builtInIcons.emplace_back(make<EnumEntry>(hstring{ name }, box_value(val)));
}
_BuiltInIcons = single_threaded_observable_vector<Editor::EnumEntry>(std::move(builtInIcons));
}
void ProfileViewModel::_DeduceCurrentIconType()
{
const auto profileIcon = IconPath();
if (profileIcon == HideIconValue)
{
_currentIconType = _IconTypes.GetAt(0);
}
else if (profileIcon.size() == 1 && (L'\uE700' <= til::at(profileIcon, 0) && til::at(profileIcon, 0) <= L'\uF8B3'))
{
_currentIconType = _IconTypes.GetAt(1);
_DeduceCurrentBuiltInIcon();
}
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(profileIcon))
{
// We already did a range check for MDL2 Assets in the previous one,
// so if we're out of that range but still short, assume we're an emoji
_currentIconType = _IconTypes.GetAt(2);
}
else
{
_currentIconType = _IconTypes.GetAt(3);
}
_NotifyChanges(L"CurrentIconType");
}
void ProfileViewModel::_DeduceCurrentBuiltInIcon()
{
if (!_BuiltInIcons)
{
_UpdateBuiltInIcons();
}
const auto profileIcon = IconPath();
for (uint32_t i = 0; i < _BuiltInIcons.Size(); i++)
{
const auto& builtIn = _BuiltInIcons.GetAt(i);
if (profileIcon == unbox_value<hstring>(builtIn.EnumValue()))
{
_CurrentBuiltInIcon = builtIn;
return;
}
}
_CurrentBuiltInIcon = _BuiltInIcons.GetAt(0);
_NotifyChanges(L"CurrentBuiltInIcon");
}
void ProfileViewModel::LeftPadding(double value) noexcept
{
if (std::abs(_parsedPadding.Left - value) >= .0001)
@@ -636,9 +549,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::hstring ProfileViewModel::LocalizedIcon() const
{
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::None)
if (UsingNoIcon())
{
return RS_(L"Profile_IconTypeNone");
return RS_(L"IconPicker_IconTypeNone");
}
return IconPath(); // For display as a string
}
@@ -652,83 +565,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return icon;
}
void ProfileViewModel::CurrentIconType(const Windows::Foundation::IInspectable& value)
bool ProfileViewModel::UsingNoIcon() const noexcept
{
if (_currentIconType != value)
{
// Switching from...
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::Image)
{
// Stash the current value of Icon. If the user
// switches out of then back to IconType::Image, we want
// the path that we display in the text box to remain unchanged.
_lastIconPath = IconPath();
}
// Set the member here instead of after setting Icon() below!
// We have an Icon property changed handler defined for when we discard changes.
// Inadvertently, that means that we call this setter again.
// Setting the member here means that we early exit at the beginning of the function
// because _currentIconType == value.
_currentIconType = value;
// Switched to...
switch (unbox_value<IconType>(value.as<Editor::EnumEntry>().EnumValue()))
{
case IconType::None:
{
IconPath(winrt::hstring{ HideIconValue });
break;
}
case IconType::Image:
{
if (!_lastIconPath.empty())
{
// Conversely, if we switch to Image,
// retrieve that saved value and apply it
IconPath(_lastIconPath);
}
break;
}
case IconType::FontIcon:
{
if (_CurrentBuiltInIcon)
{
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
}
break;
}
case IconType::Emoji:
{
// Don't set Icon here!
// Clear out the text box so we direct the user to use the emoji picker.
CurrentEmojiIcon({});
}
}
// We're not using the VM's Icon() setter above,
// so notify HasIcon changed manually
_NotifyChanges(L"CurrentIconType", L"HasIcon");
}
}
bool ProfileViewModel::UsingNoIcon() const
{
return _currentIconType == _IconTypes.GetAt(0);
}
bool ProfileViewModel::UsingBuiltInIcon() const
{
return _currentIconType == _IconTypes.GetAt(1);
}
bool ProfileViewModel::UsingEmojiIcon() const
{
return _currentIconType == _IconTypes.GetAt(2);
}
bool ProfileViewModel::UsingImageIcon() const
{
return _currentIconType == _IconTypes.GetAt(3);
const auto iconPath{ IconPath() };
return iconPath.empty() || iconPath == IconPicker::HideIconValue;
}
hstring ProfileViewModel::BellStylePreview() const

View File

@@ -4,7 +4,6 @@
#pragma once
#include "DeleteProfileEventArgs.g.h"
#include "NavigateToProfileArgs.g.h"
#include "BellSoundViewModel.g.h"
#include "ProfileViewModel.g.h"
#include "Utils.h"
@@ -12,21 +11,6 @@
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct NavigateToProfileArgs : NavigateToProfileArgsT<NavigateToProfileArgs>
{
public:
NavigateToProfileArgs(ProfileViewModel profile, Editor::IHostedInWindow windowRoot) :
_Profile(profile),
_WindowRoot(windowRoot) {}
Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; }
Editor::ProfileViewModel Profile() const noexcept { return _Profile; }
private:
Editor::IHostedInWindow _WindowRoot;
Editor::ProfileViewModel _Profile{ nullptr };
};
struct BellSoundViewModel : BellSoundViewModelT<BellSoundViewModel>, ViewModelHelper<BellSoundViewModel>
{
public:
@@ -49,13 +33,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
static void UpdateFontList() noexcept;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> CompleteFontList() noexcept { return _FontList; };
static Windows::Foundation::Collections::IObservableVector<Editor::Font> MonospaceFontList() noexcept { return _MonospaceFontList; };
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> BuiltInIcons() noexcept { return _BuiltInIcons; };
ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings, const Windows::UI::Core::CoreDispatcher& dispatcher);
Control::IControlSettings TermSettings() const;
void DeleteProfile();
void SetupAppearances(Windows::Foundation::Collections::IObservableVector<Editor::ColorSchemeViewModel> schemesList);
void ForceRefreshCurrentPage()
{
// Used to trigger the PropertyChanged handler in MainPage.cpp
// This forces the page to refresh
_NotifyChanges(L"CurrentPage");
}
// bell style bits
hstring BellStylePreview() const;
@@ -86,23 +75,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
return _profile.Icon().Resolved();
}
Windows::Foundation::IInspectable CurrentIconType() const noexcept
{
return _currentIconType;
}
Windows::UI::Xaml::Controls::IconElement IconPreview() const;
winrt::hstring LocalizedIcon() const;
void CurrentIconType(const Windows::Foundation::IInspectable& value);
bool UsingNoIcon() const;
bool UsingBuiltInIcon() const;
bool UsingEmojiIcon() const;
bool UsingImageIcon() const;
winrt::hstring IconPath() const { return _profile.Icon().Path(); }
void IconPath(const winrt::hstring& path)
{
Icon(Model::MediaResourceHelper::FromString(path));
_NotifyChanges(L"Icon", L"IconPath");
}
bool UsingNoIcon() const noexcept;
// starting directory
hstring CurrentStartingDirectoryPreview() const;
@@ -134,8 +115,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::BellSoundViewModel>, CurrentBellSounds);
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::EnumEntry, CurrentBuiltInIcon, nullptr);
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType);
@@ -166,6 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_profile, AutoMarkPrompts);
OBSERVABLE_PROJECTED_SETTING(_profile, RepositionCursorWithMouse);
OBSERVABLE_PROJECTED_SETTING(_profile, ForceVTInput);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowKittyKeyboardMode);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtChecksumReport);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtClipboardWrite);
OBSERVABLE_PROJECTED_SETTING(_profile, AnswerbackMessage);
@@ -174,7 +154,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(bool, IsBaseLayer, false);
WINRT_PROPERTY(bool, FocusDeleteButton, false);
WINRT_PROPERTY(Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable>, IconTypes);
WINRT_PROPERTY(hstring, ElementToFocus);
GETSET_BINDABLE_ENUM_SETTING(AntiAliasingMode, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode);
GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, CloseOnExit);
GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, ScrollState);
@@ -185,8 +165,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::guid _originalProfileGuid{};
winrt::hstring _lastBgImagePath;
winrt::hstring _lastStartingDirectoryPath;
winrt::hstring _lastIconPath;
Windows::Foundation::IInspectable _currentIconType{};
Editor::AppearanceViewModel _defaultAppearanceViewModel;
Windows::UI::Core::CoreDispatcher _dispatcher;
@@ -197,13 +175,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _MarkDuplicateBellSoundDirectories();
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _BuiltInIcons;
Model::CascadiaSettings _appSettings;
Editor::AppearanceViewModel _unfocusedAppearanceViewModel;
void _UpdateBuiltInIcons();
void _DeduceCurrentIconType();
void _DeduceCurrentBuiltInIcon();
};
struct DeleteProfileEventArgs :

View File

@@ -14,12 +14,6 @@ import "ColorSchemesPageViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass NavigateToProfileArgs
{
ProfileViewModel Profile { get; };
IHostedInWindow WindowRoot { get; };
}
runtimeclass DeleteProfileEventArgs
{
Guid ProfileGuid { get; };
@@ -42,14 +36,6 @@ namespace Microsoft.Terminal.Settings.Editor
Advanced = 3
};
enum IconType
{
None = 0,
FontIcon,
Image,
Emoji
};
runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
event Windows.Foundation.TypedEventHandler<ProfileViewModel, DeleteProfileEventArgs> DeleteProfileRequested;
@@ -107,17 +93,8 @@ namespace Microsoft.Terminal.Settings.Editor
Windows.UI.Xaml.Controls.IconElement IconPreview { get; };
String EvaluatedIcon { get; };
String LocalizedIcon { get; };
String CurrentEmojiIcon;
IInspectable CurrentIconType;
Windows.Foundation.Collections.IVector<IInspectable> IconTypes { get; };
Boolean UsingNoIcon { get; };
Boolean UsingBuiltInIcon { get; };
Boolean UsingEmojiIcon { get; };
Boolean UsingImageIcon { get; };
String IconPath;
EnumEntry CurrentBuiltInIcon;
Windows.Foundation.Collections.IObservableVector<EnumEntry> BuiltInIcons { get; };
Boolean UsingNoIcon { get; };
String TabTitlePreview { get; };
String AnswerbackMessagePreview { get; };
@@ -157,6 +134,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AutoMarkPrompts);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RepositionCursorWithMouse);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ForceVTInput);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, AnswerbackMessage);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RainbowSuggestions);

View File

@@ -25,9 +25,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Profiles_Advanced::OnNavigatedTo(const NavigationEventArgs& e)
{
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_Profile = args.ViewModel().as<Editor::ProfileViewModel>();
_windowRoot = args.WindowRoot();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -37,7 +37,8 @@
<StackPanel Grid.Row="1"
Style="{StaticResource SettingsStackStyle}">
<!-- Antialiasing Mode -->
<local:SettingContainer x:Uid="Profile_AntialiasingMode"
<local:SettingContainer x:Name="AntialiasingMode"
x:Uid="Profile_AntialiasingMode"
ClearSettingValue="{x:Bind Profile.ClearAntialiasingMode}"
HasSettingValue="{x:Bind Profile.HasAntialiasingMode, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AntialiasingModeOverrideSource, Mode=OneWay}">
@@ -49,7 +50,8 @@
</local:SettingContainer>
<!-- AltGr Aliasing -->
<local:SettingContainer x:Uid="Profile_AltGrAliasing"
<local:SettingContainer x:Name="AltGrAliasing"
x:Uid="Profile_AltGrAliasing"
ClearSettingValue="{x:Bind Profile.ClearAltGrAliasing}"
HasSettingValue="{x:Bind Profile.HasAltGrAliasing, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AltGrAliasingOverrideSource, Mode=OneWay}">
@@ -58,7 +60,8 @@
</local:SettingContainer>
<!-- Snap On Input -->
<local:SettingContainer x:Uid="Profile_SnapOnInput"
<local:SettingContainer x:Name="SnapOnInput"
x:Uid="Profile_SnapOnInput"
ClearSettingValue="{x:Bind Profile.ClearSnapOnInput}"
HasSettingValue="{x:Bind Profile.HasSnapOnInput, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.SnapOnInputOverrideSource, Mode=OneWay}">
@@ -67,7 +70,8 @@
</local:SettingContainer>
<!-- History Size -->
<local:SettingContainer x:Uid="Profile_HistorySize"
<local:SettingContainer x:Name="HistorySize"
x:Uid="Profile_HistorySize"
ClearSettingValue="{x:Bind Profile.ClearHistorySize}"
HasSettingValue="{x:Bind Profile.HasHistorySize, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.HistorySizeOverrideSource, Mode=OneWay}">
@@ -80,7 +84,8 @@
</local:SettingContainer>
<!-- Close On Exit -->
<local:SettingContainer x:Uid="Profile_CloseOnExit"
<local:SettingContainer x:Name="CloseOnExit"
x:Uid="Profile_CloseOnExit"
ClearSettingValue="{x:Bind Profile.ClearCloseOnExit}"
HasSettingValue="{x:Bind Profile.HasCloseOnExit, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.CloseOnExitOverrideSource, Mode=OneWay}">
@@ -92,7 +97,8 @@
</local:SettingContainer>
<!-- Bell Style -->
<local:SettingContainer x:Uid="Profile_BellStyle"
<local:SettingContainer x:Name="BellStyle"
x:Uid="Profile_BellStyle"
ClearSettingValue="{x:Bind Profile.ClearBellStyle}"
CurrentValue="{x:Bind Profile.BellStylePreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasBellStyle, Mode=OneWay}"
@@ -109,7 +115,8 @@
</local:SettingContainer>
<!-- Bell Sound -->
<local:SettingContainer x:Uid="Profile_BellSound"
<local:SettingContainer x:Name="BellSound"
x:Uid="Profile_BellSound"
ClearSettingValue="{x:Bind Profile.ClearBellSound}"
CurrentValue="{x:Bind Profile.BellSoundPreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasBellSound, Mode=OneWay}"
@@ -190,7 +197,8 @@
</local:SettingContainer>
<!-- RightClickContextMenu -->
<local:SettingContainer x:Uid="Profile_RightClickContextMenu"
<local:SettingContainer x:Name="RightClickContextMenu"
x:Uid="Profile_RightClickContextMenu"
ClearSettingValue="{x:Bind Profile.ClearRightClickContextMenu}"
HasSettingValue="{x:Bind Profile.HasRightClickContextMenu, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.RightClickContextMenuOverrideSource, Mode=OneWay}">
@@ -199,7 +207,8 @@
</local:SettingContainer>
<!-- ShowMarks -->
<local:SettingContainer x:Uid="Profile_ShowMarks"
<local:SettingContainer x:Name="ShowMarks"
x:Uid="Profile_ShowMarks"
ClearSettingValue="{x:Bind Profile.ClearShowMarks}"
HasSettingValue="{x:Bind Profile.HasShowMarks, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.ShowMarksOverrideSource, Mode=OneWay}"
@@ -209,7 +218,8 @@
</local:SettingContainer>
<!-- AutoMarkPrompts -->
<local:SettingContainer x:Uid="Profile_AutoMarkPrompts"
<local:SettingContainer x:Name="AutoMarkPrompts"
x:Uid="Profile_AutoMarkPrompts"
ClearSettingValue="{x:Bind Profile.ClearAutoMarkPrompts}"
HasSettingValue="{x:Bind Profile.HasAutoMarkPrompts, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AutoMarkPromptsOverrideSource, Mode=OneWay}"
@@ -219,7 +229,8 @@
</local:SettingContainer>
<!-- ReloadEnvVars -->
<local:SettingContainer x:Uid="Profile_ReloadEnvVars"
<local:SettingContainer x:Name="ReloadEnvVars"
x:Uid="Profile_ReloadEnvVars"
ClearSettingValue="{x:Bind Profile.ClearReloadEnvironmentVariables}"
HasSettingValue="{x:Bind Profile.HasReloadEnvironmentVariables, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.ReloadEnvironmentVariablesOverrideSource, Mode=OneWay}">
@@ -228,7 +239,8 @@
</local:SettingContainer>
<!-- RepositionCursorWithMouse -->
<local:SettingContainer x:Uid="Profile_RepositionCursorWithMouse"
<local:SettingContainer x:Name="RepositionCursorWithMouse"
x:Uid="Profile_RepositionCursorWithMouse"
ClearSettingValue="{x:Bind Profile.ClearRepositionCursorWithMouse}"
HasSettingValue="{x:Bind Profile.HasRepositionCursorWithMouse, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.RepositionCursorWithMouseOverrideSource, Mode=OneWay}"
@@ -238,7 +250,8 @@
</local:SettingContainer>
<!-- RainbowSuggestions -->
<local:SettingContainer x:Uid="Profile_RainbowSuggestions"
<local:SettingContainer x:Name="RainbowSuggestions"
x:Uid="Profile_RainbowSuggestions"
ClearSettingValue="{x:Bind Profile.ClearRainbowSuggestions}"
HasSettingValue="{x:Bind Profile.HasRainbowSuggestions, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.RainbowSuggestionsOverrideSource, Mode=OneWay}">
@@ -247,7 +260,8 @@
</local:SettingContainer>
<!-- Path Translation -->
<local:SettingContainer x:Uid="Profile_PathTranslationStyle"
<local:SettingContainer x:Name="PathTranslationStyle"
x:Uid="Profile_PathTranslationStyle"
ClearSettingValue="{x:Bind Profile.ClearPathTranslationStyle}"
HasSettingValue="{x:Bind Profile.HasPathTranslationStyle, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.PathTranslationStyleOverrideSource, Mode=OneWay}">

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "Profiles_Appearance.h"
#include "Appearances.h"
#include "ProfileViewModel.h"
#include "PreviewConnection.h"
@@ -12,6 +13,8 @@
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
static constexpr std::wstring_view AppearanceSettingPrefix{ L"App." };
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Profiles_Appearance::Profiles_Appearance()
@@ -22,10 +25,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Profiles_Appearance::OnNavigatedTo(const NavigationEventArgs& e)
{
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_Profile = args.ViewModel().as<Editor::ProfileViewModel>();
_windowRoot = args.WindowRoot();
// Settings are stored in Profiles_Appearance and Appearances.
// We use the "App." prefix to indicate if it's in Appearances,
// and remove it on the way to Appearances object.
const auto elementToFocus = args.ElementToFocus();
if (elementToFocus.starts_with(AppearanceSettingPrefix))
{
std::wstring correctedName{ elementToFocus.c_str() };
get_self<implementation::Appearances>(DefaultAppearanceView())->BringIntoViewWhenLoaded(hstring{ correctedName.substr(AppearanceSettingPrefix.size()) });
}
else
{
BringIntoViewWhenLoaded(elementToFocus);
}
if (!_previewControl)
{
const auto settings = winrt::get_self<implementation::ProfileViewModel>(_Profile)->TermSettings();

View File

@@ -5,11 +5,10 @@
#include <ThrottledFunc.h>
#include "Profiles_Appearance.g.h"
#include "PreviewConnection.h"
#include "Utils.h"
#include "Profiles_Appearance.g.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct Profiles_Appearance : public HasScrollViewer<Profiles_Appearance>, Profiles_AppearanceT<Profiles_Appearance>

View File

@@ -75,7 +75,8 @@
CornerRadius="{StaticResource ControlCornerRadius}" />
</Border>
<local:Appearances Appearance="{x:Bind Profile.DefaultAppearance, Mode=OneWay}"
<local:Appearances x:Name="DefaultAppearanceView"
Appearance="{x:Bind Profile.DefaultAppearance, Mode=OneWay}"
SourceProfile="{x:Bind Profile, Mode=OneWay}"
WindowRoot="{x:Bind WindowRoot, Mode=OneTime}" />
@@ -85,12 +86,12 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Opacity -->
<local:SettingContainer x:Name="OpacityContainer"
<local:SettingContainer x:Name="Opacity"
x:Uid="Profile_Opacity"
ClearSettingValue="{x:Bind Profile.ClearOpacity}"
HasSettingValue="{x:Bind Profile.HasOpacity, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.OpacityOverrideSource, Mode=OneWay}">
<StackPanel x:Name="OpacityControl">
<StackPanel>
<Grid Style="{StaticResource CustomSliderControlGridStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -107,12 +108,12 @@
</local:SettingContainer>
<!-- Use Acrylic -->
<local:SettingContainer x:Uid="Profile_UseAcrylic"
<local:SettingContainer x:Name="UseAcrylic"
x:Uid="Profile_UseAcrylic"
ClearSettingValue="{x:Bind Profile.ClearUseAcrylic}"
HasSettingValue="{x:Bind Profile.HasUseAcrylic, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.UseAcrylicOverrideSource, Mode=OneWay}">
<ToggleSwitch x:Name="UseAcrylicToggleSwitch"
IsOn="{x:Bind Profile.UseAcrylic, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind Profile.UseAcrylic, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
@@ -124,7 +125,8 @@
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Padding -->
<local:SettingContainer x:Uid="Profile_Padding"
<local:SettingContainer x:Name="Padding"
x:Uid="Profile_Padding"
ClearSettingValue="{x:Bind Profile.ClearPadding}"
CurrentValue="{x:Bind Profile.Padding, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasPadding, Mode=OneWay}"
@@ -191,7 +193,8 @@
</local:SettingContainer>
<!-- Scrollbar Visibility -->
<local:SettingContainer x:Uid="Profile_ScrollbarVisibility"
<local:SettingContainer x:Name="ScrollbarVisibility"
x:Uid="Profile_ScrollbarVisibility"
ClearSettingValue="{x:Bind Profile.ClearScrollState}"
HasSettingValue="{x:Bind Profile.HasScrollState, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.ScrollStateOverrideSource, Mode=OneWay}">
@@ -207,7 +210,10 @@
Visibility="{x:Bind Profile.EditableUnfocusedAppearance, Mode=OneWay}">
<TextBlock x:Uid="Profile_UnfocusedAppearanceTextBlock"
Style="{StaticResource TextBlockSubtitleStyle}" />
<Button x:Uid="Profile_CreateUnfocusedAppearanceButton"
<!-- Create Unfocused Appearance -->
<Button x:Name="CreateUnfocusedAppearance"
x:Uid="Profile_CreateUnfocusedAppearanceButton"
Margin="8,0,0,0"
VerticalAlignment="Bottom"
Click="CreateUnfocusedAppearance_Click"
@@ -224,7 +230,10 @@
</StackPanel>
</Button.Content>
</Button>
<Button x:Uid="Profile_DeleteUnfocusedAppearanceButton"
<!-- Delete Unfocused Appearance -->
<Button x:Name="DeleteUnfocusedAppearance"
x:Uid="Profile_DeleteUnfocusedAppearanceButton"
Margin="8,0,0,0"
VerticalAlignment="Bottom"
Click="DeleteUnfocusedAppearance_Click"
@@ -241,7 +250,10 @@
</Button.Content>
</Button>
</StackPanel>
<local:Appearances Appearance="{x:Bind Profile.UnfocusedAppearance, Mode=OneWay}"
<!-- Unfocused Appearance -->
<local:Appearances x:Name="UnfocusedAppearanceView"
Appearance="{x:Bind Profile.UnfocusedAppearance, Mode=OneWay}"
SourceProfile="{x:Bind Profile, Mode=OneWay}"
Visibility="{x:Bind Profile.ShowUnfocusedAppearance, Mode=OneWay}"
WindowRoot="{x:Bind WindowRoot, Mode=OneTime}" />

View File

@@ -29,9 +29,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Profiles_Base::OnNavigatedTo(const NavigationEventArgs& e)
{
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_Profile = args.ViewModel().as<Editor::ProfileViewModel>();
_windowRoot = args.WindowRoot();
BringIntoViewWhenLoaded(args.ElementToFocus());
// Check the use parent directory box if the starting directory is empty
if (_Profile.StartingDirectory().empty())
@@ -133,18 +134,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
safe_void_coroutine Profiles_Base::Icon_Click(const IInspectable&, const RoutedEventArgs&)
{
auto lifetime = get_strong();
const auto parentHwnd{ reinterpret_cast<HWND>(_windowRoot.GetHostingWindow()) };
auto file = co_await OpenImagePicker(parentHwnd);
if (!file.empty())
{
_Profile.IconPath(file);
}
}
safe_void_coroutine Profiles_Base::StartingDirectory_Click(const IInspectable&, const RoutedEventArgs&)
{
auto lifetime = get_strong();
@@ -169,77 +158,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_Profile.StartingDirectory(folder);
}
}
IconSource Profiles_Base::BuiltInIconConverter(const IInspectable& iconVal)
{
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
}
void Profiles_Base::BuiltInIconPicker_GotFocus(const IInspectable& sender, const RoutedEventArgs& /*e*/)
{
_updateIconFilter({});
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
}
void Profiles_Base::BuiltInIconPicker_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& e)
{
const auto iconEntry = unbox_value_or<EnumEntry>(e.ChosenSuggestion(), nullptr);
if (!iconEntry)
{
return;
}
_Profile.CurrentBuiltInIcon(iconEntry);
}
void Profiles_Base::BuiltInIconPicker_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& e)
{
if (e.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
{
return;
}
std::wstring_view filter{ sender.Text() };
filter = til::trim(filter, L' ');
_updateIconFilter(filter);
}
void Profiles_Base::_updateIconFilter(std::wstring_view filter)
{
if (_iconFilter != filter)
{
_filteredBuiltInIcons = nullptr;
_iconFilter = filter;
_updateFilteredIconList();
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredBuiltInIconList" });
}
}
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> Profiles_Base::FilteredBuiltInIconList()
{
if (!_filteredBuiltInIcons)
{
_updateFilteredIconList();
}
return _filteredBuiltInIcons;
}
void Profiles_Base::_updateFilteredIconList()
{
_filteredBuiltInIcons = ProfileViewModel::BuiltInIcons();
if (_iconFilter.empty())
{
return;
}
// Find matching icons and populate the filtered list
std::vector<Editor::EnumEntry> filtered;
filtered.reserve(_filteredBuiltInIcons.Size());
for (const auto& icon : _filteredBuiltInIcons)
{
if (til::contains_linguistic_insensitive(icon.EnumName(), _iconFilter))
{
filtered.emplace_back(icon);
}
}
_filteredBuiltInIcons = winrt::single_threaded_observable_vector(std::move(filtered));
}
}

View File

@@ -18,32 +18,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void OnNavigatedFrom(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
safe_void_coroutine StartingDirectory_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
safe_void_coroutine Commandline_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void Appearance_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void Terminal_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void Advanced_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void DeleteConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> FilteredBuiltInIconList();
void BuiltInIconPicker_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void BuiltInIconPicker_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& e);
void BuiltInIconPicker_QuerySubmitted(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& e);
static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);
til::property_changed_event PropertyChanged;
Editor::IHostedInWindow WindowRoot() const noexcept { return _windowRoot; }
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
private:
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
Editor::IHostedInWindow _windowRoot;
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _filteredBuiltInIcons;
std::wstring _iconFilter;
void _updateIconFilter(std::wstring_view filter);
void _updateFilteredIconList();
};
};

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
import "ProfileViewModel.idl";
import "MainPage.idl";
namespace Microsoft.Terminal.Settings.Editor
{
@@ -9,8 +10,6 @@ namespace Microsoft.Terminal.Settings.Editor
{
Profiles_Base();
ProfileViewModel Profile { get; };
Windows.Foundation.Collections.IObservableVector<EnumEntry> FilteredBuiltInIconList { get; };
static Windows.UI.Xaml.Controls.IconSource BuiltInIconConverter(IInspectable iconVal);
IHostedInWindow WindowRoot { get; };
}
}

View File

@@ -40,7 +40,8 @@
Additionally, the JSON stubs generated by auto-generated profiles come with a name,
so the name will always be overridden.
-->
<local:SettingContainer x:Uid="Profile_Name"
<local:SettingContainer x:Name="Name"
x:Uid="Profile_Name"
CurrentValue="{x:Bind Profile.Name, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.IsBaseLayer), Mode=OneWay}">
@@ -49,7 +50,7 @@
</local:SettingContainer>
<!-- Commandline -->
<local:SettingContainer x:Name="CommandlineContainer"
<local:SettingContainer x:Name="Commandline"
x:Uid="Profile_Commandline"
ClearSettingValue="{x:Bind Profile.ClearCommandline}"
CurrentValue="{x:Bind Profile.Commandline, Mode=OneWay}"
@@ -70,7 +71,7 @@
</local:SettingContainer>
<!-- Starting Directory -->
<local:SettingContainer x:Name="StartingDirectoryContainer"
<local:SettingContainer x:Name="StartingDirectory"
x:Uid="Profile_StartingDirectory"
ClearSettingValue="{x:Bind Profile.ClearStartingDirectory}"
CurrentValue="{x:Bind Profile.CurrentStartingDirectoryPreview, Mode=OneWay}"
@@ -99,7 +100,8 @@
</local:SettingContainer>
<!-- Icon -->
<local:SettingContainer x:Uid="Profile_Icon"
<local:SettingContainer x:Name="Icon"
x:Uid="Profile_Icon"
ClearSettingValue="{x:Bind Profile.ClearIcon}"
CurrentValueAccessibleName="{x:Bind Profile.LocalizedIcon, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasIcon, Mode=OneWay}"
@@ -122,87 +124,14 @@
</Grid>
</local:SettingContainer.CurrentValue>
<local:SettingContainer.Content>
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<!-- Icon Type -->
<ComboBox x:Uid="Profile_IconType"
Grid.Column="0"
ItemsSource="{x:Bind Profile.IconTypes}"
SelectedItem="{x:Bind Profile.CurrentIconType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="local:EnumEntry">
<TextBlock Text="{x:Bind EnumName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Built-In Icon -->
<AutoSuggestBox x:Uid="Profile_BuiltInIcon"
Grid.Column="1"
GotFocus="BuiltInIconPicker_GotFocus"
ItemsSource="{x:Bind FilteredBuiltInIconList, Mode=OneWay}"
QuerySubmitted="BuiltInIconPicker_QuerySubmitted"
Text="{x:Bind Profile.CurrentBuiltInIcon.EnumName, Mode=OneWay}"
TextBoxStyle="{StaticResource TextBoxSettingStyle}"
TextChanged="BuiltInIconPicker_TextChanged"
Visibility="{x:Bind Profile.UsingBuiltInIcon, Mode=OneWay}">
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="local:EnumEntry">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<IconSourceElement Grid.Column="0"
Width="16"
Height="16"
IconSource="{x:Bind local:Profiles_Base.BuiltInIconConverter(EnumValue), Mode=OneTime}" />
<TextBlock Grid.Column="1"
Text="{x:Bind EnumName}" />
</Grid>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
<!-- Image (File) Icon -->
<TextBox x:Uid="Profile_IconBox"
Grid.Column="1"
MaxWidth="Infinity"
HorizontalAlignment="Stretch"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind Profile.IconPath, Mode=TwoWay}"
Visibility="{x:Bind Profile.UsingImageIcon, Mode=OneWay}" />
<Button x:Uid="Profile_IconBrowse"
Grid.Column="2"
Margin="0"
VerticalAlignment="Top"
Click="Icon_Click"
Style="{StaticResource BrowseButtonStyle}"
Visibility="{x:Bind Profile.UsingImageIcon, Mode=OneWay}" />
<!-- Emoji Icon -->
<TextBox x:Uid="Profile_IconEmojiBox"
Grid.Column="1"
MaxWidth="Infinity"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind Profile.CurrentEmojiIcon, Mode=TwoWay}"
Visibility="{x:Bind Profile.UsingEmojiIcon, Mode=OneWay}" />
</Grid>
<local:IconPicker CurrentIconPath="{x:Bind Profile.IconPath, Mode=TwoWay}"
WindowRoot="{x:Bind WindowRoot, Mode=OneWay}" />
</local:SettingContainer.Content>
</local:SettingContainer>
<!-- Tab Title -->
<local:SettingContainer x:Uid="Profile_TabTitle"
<local:SettingContainer x:Name="TabTitle"
x:Uid="Profile_TabTitle"
ClearSettingValue="{x:Bind Profile.ClearTabTitle}"
CurrentValue="{x:Bind Profile.TabTitlePreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasTabTitle, Mode=OneWay}"
@@ -229,7 +158,8 @@
</local:SettingContainer>
<!-- Elevate -->
<local:SettingContainer x:Uid="Profile_Elevate"
<local:SettingContainer x:Name="Elevate"
x:Uid="Profile_Elevate"
ClearSettingValue="{x:Bind Profile.ClearElevate}"
HasSettingValue="{x:Bind Profile.HasElevate, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.ElevateOverrideSource, Mode=OneWay}">
@@ -238,7 +168,8 @@
</local:SettingContainer>
<!-- Hidden -->
<local:SettingContainer x:Uid="Profile_Hidden"
<local:SettingContainer x:Name="Hidden"
x:Uid="Profile_Hidden"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.IsBaseLayer), Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind Profile.Hidden, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />

View File

@@ -22,8 +22,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Profiles_Base_Orphaned::OnNavigatedTo(const NavigationEventArgs& e)
{
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_Profile = args.ViewModel().as<Editor::ProfileViewModel>();
_layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// This event fires every time the layout changes, but it is always the last one to fire

View File

@@ -23,7 +23,8 @@
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Delete Button -->
<local:SettingContainer x:Uid="Profile_Delete_Orphaned">
<local:SettingContainer x:Name="DeleteOrphaned"
x:Uid="Profile_Delete_Orphaned">
<local:SettingContainer.Content>
<Button x:Name="DeleteButton"
Click="DeleteConfirmation_Click"
@@ -40,7 +41,8 @@
</local:SettingContainer.Content>
</local:SettingContainer>
<local:SettingContainer x:Uid="Profile_Name">
<local:SettingContainer x:Name="Name"
x:Uid="Profile_Name">
<local:SettingContainer.Content>
<TextBlock FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
@@ -48,7 +50,8 @@
</local:SettingContainer.Content>
</local:SettingContainer>
<local:SettingContainer x:Uid="Profile_Source_Orphaned">
<local:SettingContainer x:Name="Source"
x:Uid="Profile_Source_Orphaned">
<local:SettingContainer.Content>
<TextBlock FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"

View File

@@ -20,7 +20,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Profiles_Terminal::OnNavigatedTo(const NavigationEventArgs& e)
{
_Profile = e.Parameter().as<Editor::ProfileViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_Profile = args.ViewModel().as<Editor::ProfileViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -32,7 +32,8 @@
Style="{StaticResource SettingsStackStyle}">
<!-- Suppress Application Title -->
<local:SettingContainer x:Uid="Profile_SuppressApplicationTitle"
<local:SettingContainer x:Name="SuppressApplicationTitle"
x:Uid="Profile_SuppressApplicationTitle"
ClearSettingValue="{x:Bind Profile.ClearSuppressApplicationTitle}"
HasSettingValue="{x:Bind Profile.HasSuppressApplicationTitle, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.SuppressApplicationTitleOverrideSource, Mode=OneWay}">
@@ -41,7 +42,8 @@
</local:SettingContainer>
<!-- Force VT Input -->
<local:SettingContainer x:Uid="Profile_ForceVTInput"
<local:SettingContainer x:Name="ForceVTInput"
x:Uid="Profile_ForceVTInput"
ClearSettingValue="{x:Bind Profile.ClearForceVTInput}"
HasSettingValue="{x:Bind Profile.HasForceVTInput, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.ForceVTInputOverrideSource, Mode=OneWay}">
@@ -49,8 +51,19 @@
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Kitty Keyboard Mode -->
<local:SettingContainer x:Name="AllowKittyKeyboardMode"
x:Uid="Profile_AllowKittyKeyboardMode"
ClearSettingValue="{x:Bind Profile.ClearAllowKittyKeyboardMode}"
HasSettingValue="{x:Bind Profile.HasAllowKittyKeyboardMode, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AllowKittyKeyboardModeOverrideSource, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind Profile.AllowKittyKeyboardMode, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Allow VT Checksum Report -->
<local:SettingContainer x:Uid="Profile_AllowVtChecksumReport"
<local:SettingContainer x:Name="AllowVtChecksumReport"
x:Uid="Profile_AllowVtChecksumReport"
ClearSettingValue="{x:Bind Profile.ClearAllowVtChecksumReport}"
HasSettingValue="{x:Bind Profile.HasAllowVtChecksumReport, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AllowVtChecksumReportOverrideSource, Mode=OneWay}">
@@ -59,7 +72,8 @@
</local:SettingContainer>
<!-- Allow VT Clipboard Writing -->
<local:SettingContainer x:Uid="Profile_AllowVtClipboardWrite"
<local:SettingContainer x:Name="AllowVtClipboardWrite"
x:Uid="Profile_AllowVtClipboardWrite"
ClearSettingValue="{x:Bind Profile.ClearAllowVtClipboardWrite}"
HasSettingValue="{x:Bind Profile.HasAllowVtClipboardWrite, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AllowVtClipboardWriteOverrideSource, Mode=OneWay}">
@@ -68,7 +82,8 @@
</local:SettingContainer>
<!-- Answerback Message -->
<local:SettingContainer x:Uid="Profile_AnswerbackMessage"
<local:SettingContainer x:Name="AnswerbackMessage"
x:Uid="Profile_AnswerbackMessage"
ClearSettingValue="{x:Bind Profile.ClearAnswerbackMessage}"
CurrentValue="{x:Bind Profile.AnswerbackMessagePreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasAnswerbackMessage, Mode=OneWay}"

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "Rendering.h"
#include "NavigateToPageArgs.g.h"
#include "Rendering.g.cpp"
using namespace winrt::Windows::UI::Xaml::Navigation;
@@ -16,7 +17,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Rendering::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::RenderingViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
_ViewModel = args.ViewModel().as<Editor::RenderingViewModel>();
BringIntoViewWhenLoaded(args.ElementToFocus());
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,

View File

@@ -24,7 +24,8 @@
</Page.Resources>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<local:SettingContainer x:Uid="Globals_GraphicsAPI">
<local:SettingContainer x:Name="GraphicsAPI"
x:Uid="Globals_GraphicsAPI">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.GraphicsAPIList}"
@@ -32,12 +33,14 @@
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
<local:SettingContainer x:Uid="Globals_DisablePartialInvalidation">
<local:SettingContainer x:Name="DisablePartialInvalidation"
x:Uid="Globals_DisablePartialInvalidation">
<ToggleSwitch IsOn="{x:Bind ViewModel.DisablePartialInvalidation, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<local:SettingContainer x:Uid="Globals_SoftwareRendering">
<local:SettingContainer x:Name="SoftwareRendering"
x:Uid="Globals_SoftwareRendering">
<ToggleSwitch IsOn="{x:Bind ViewModel.SoftwareRendering, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>

View File

@@ -546,6 +546,14 @@
<value>Use the legacy input encoding</value>
<comment>Header for a control to toggle legacy input encoding for the terminal.</comment>
</data>
<data name="Profile_AllowKittyKeyboardMode.Header" xml:space="preserve">
<value>Kitty keyboard protocol mode</value>
<comment>Header for a control to set the kitty keyboard protocol mode.</comment>
</data>
<data name="Profile_AllowKittyKeyboardMode.HelpText" xml:space="preserve">
<value>Sets the baseline flags for the kitty keyboard protocol. Value is a sum of: 1=Disambiguate, 2=Report event types, 4=Report alternate keys, 8=Report all keys, 16=Report text.</value>
<comment>Additional description for what the "kitty keyboard mode" setting does.</comment>
</data>
<data name="Profile_AllowVtChecksumReport.Header" xml:space="preserve">
<value>Allow DECRQCRA (Request Checksum of Rectangular Area)</value>
<comment>{Locked="DECRQCRA"}{Locked="Request Checksum of Rectangular Area"}Header for a control to toggle support for the DECRQCRA control sequence.</comment>
@@ -1110,11 +1118,11 @@
<value>Icon</value>
<comment>Header for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
</data>
<data name="Profile_IconBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<data name="IconPicker_ImagePathBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Icon</value>
<comment>Name for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
<comment>Name for a control to determine what icon can be used. This is not necessarily a file path, but can be one. It's usually used for images.</comment>
</data>
<data name="Profile_IconEmojiBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<data name="IconPicker_EmojiBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Icon</value>
<comment>Name for a control to determine what icon can be used to represent this profile.</comment>
</data>
@@ -1122,7 +1130,7 @@
<value>Emoji or image file location of the icon used in the profile.</value>
<comment>A description for what the "icon" setting does. Presented near "Profile_Icon".</comment>
</data>
<data name="Profile_IconBrowse.Content" xml:space="preserve">
<data name="IconPicker_IconBrowse.Content" xml:space="preserve">
<value>Browse...</value>
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
</data>
@@ -2318,31 +2326,31 @@
<value>Use theme color</value>
<comment>Label for a button directing the user to use the tab color defined in the terminal's current theme.</comment>
</data>
<data name="Profile_IconTypeNone" xml:space="preserve">
<data name="IconPicker_IconTypeNone" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "icon style" dropdown. When selected, there will be no icon for the profile.</comment>
<comment>An option to choose from for the "icon style" dropdown. When selected, there will be no icon set.</comment>
</data>
<data name="Profile_IconTypeImage" xml:space="preserve">
<data name="IconPicker_IconTypeImage" xml:space="preserve">
<value>File</value>
<comment>An option to choose from for the "icon style" dropdown. When selected, a custom image can set for the profile's icon.</comment>
<comment>An option to choose from for the "icon style" dropdown. When selected, a custom image can set as the icon.</comment>
</data>
<data name="Profile_IconTypeEmoji" xml:space="preserve">
<data name="IconPicker_IconTypeEmoji" xml:space="preserve">
<value>Emoji</value>
<comment>An option to choose from for the "icon style" dropdown. When selected, an emoji can be set for the profile's icon.</comment>
<comment>An option to choose from for the "icon style" dropdown. When selected, an emoji can be set as the icon.</comment>
</data>
<data name="Profile_IconTypeFontIcon" xml:space="preserve">
<data name="IconPicker_IconTypeFontIcon" xml:space="preserve">
<value>Built-in Icon</value>
<comment>An option to choose from for the "icon style" dropdown. When selected, the user can choose from several preselected options to set the profile's icon.</comment>
<comment>An option to choose from for the "icon style" dropdown. When selected, the user can choose from several preselected options to set as the icon.</comment>
</data>
<data name="Profile_IconEmojiBox.PlaceholderText" xml:space="preserve">
<data name="IconPicker_EmojiBox.PlaceholderText" xml:space="preserve">
<value>Use "Win + period" to open the emoji picker</value>
<comment>"Win + period" refers to the OS key binding to open the emoji picker.</comment>
</data>
<data name="Profile_IconType.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<data name="IconPicker_IconType.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Icon type</value>
<comment>Accessible name for a control allowing the user to select the type of icon they would like to use.</comment>
</data>
<data name="Profile_BuiltInIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<data name="IconPicker_BuiltInIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Icon</value>
<comment>Accessible name for a control allowing the user to select the icon from a list of built in icons.</comment>
</data>
@@ -2446,6 +2454,14 @@
<value>Folder Name</value>
<comment>Header for a control that allows the user to modify the name of the current folder entry.</comment>
</data>
<data name="NewTabMenu_CurrentFolderIcon.Header" xml:space="preserve">
<value>Folder Icon</value>
<comment>Header for a control that allows the user to modify the icon of the current folder entry.</comment>
</data>
<data name="NewTabMenu_CurrentFolderIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Folder Icon</value>
<comment>Name for a control to that allows the user to modify the icon of the current folder entry.</comment>
</data>
<data name="NewTabMenu_CurrentFolderInlining.Header" xml:space="preserve">
<value>Allow inlining</value>
<comment>Header for a control that allows the nested entries to be presented inline rather than with a folder.</comment>
@@ -2575,19 +2591,19 @@
<comment>An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
<value>WSL (C:\ -> /mnt/c)</value>
<value>WSL (C:\ -&gt; /mnt/c)</value>
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
<value>Cygwin (C:\ -> /cygdrive/c)</value>
<value>Cygwin (C:\ -&gt; /cygdrive/c)</value>
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
<value>MSYS2 (C:\ -> /c)</value>
<value>MSYS2 (C:\ -&gt; /c)</value>
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
<value>MinGW (C:\ -> C:/)</value>
<value>MinGW (C:\ -&gt; C:/)</value>
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
@@ -2701,4 +2717,16 @@
<data name="Settings_ResetApplicationStateConfirmationButton.Content" xml:space="preserve">
<value>Yes, clear the cache</value>
</data>
</root>
<data name="Nav_SearchBox.PlaceholderText" xml:space="preserve">
<value>Search for settings</value>
<comment>Placeholder text for the main search box in the settings editor.</comment>
</data>
<data name="Search_NoResults" xml:space="preserve">
<value>No results for "{}"</value>
<comment>{Locked="{}"} Displayed when no results were found for a given query. "{}" will be replaced with the query.</comment>
</data>
<data name="IconPicker_BuiltInIcon.PlaceholderText" xml:space="preserve">
<value>Type to filter icons</value>
<comment>Placeholder text for a text box to filter and select an icon.</comment>
</data>
</root>

View File

@@ -0,0 +1,476 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "SearchIndex.h"
#include "FilteredSearchResult.g.cpp"
#include "SearchResultTemplateSelector.g.cpp"
#include "NavConstants.h"
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include <ScopedResourceLoader.h>
// Weight multipliers for search result scoring.
// Higher values prioritize certain types of matches over others.
static constexpr int WeightRuntimeObjectMatch = 6; // Direct runtime object name match (e.g., "PowerShell")
static constexpr int WeightProfileDefaults = 6; // Profile Defaults setting
static constexpr int WeightRuntimeObjectSetting = 5; // Setting with runtime object context (e.g., "PowerShell: Command line")
static constexpr int WeightDisplayTextLocalized = 5; // Display text in current locale
static constexpr int WeightDisplayTextNeutral = 2; // Display text in English (fallback)
// Minimum fzf score threshold to filter out low-quality fuzzy matches
static constexpr int MinimumMatchScore = 100;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// Retrieves the searchable fields from the LocalizedIndexEntry and their associated weight bonus.
// This allows us to prioritize certain fields over others when scoring search results.
std::array<std::pair<std::optional<winrt::hstring>, int>, 2> LocalizedIndexEntry::GetSearchableFields() const
{
// Profile Defaults entries (DisplayTextUid starts with "Profile_") get a higher weight
const auto weight = til::starts_with(std::wstring_view{ Entry->DisplayTextUid }, L"Profile_") ? WeightProfileDefaults : WeightDisplayTextLocalized;
return { { { std::optional<winrt::hstring>{ Entry->DisplayTextLocalized }, weight },
{ DisplayTextNeutral, WeightDisplayTextNeutral } } };
}
const ScopedResourceLoader& EnglishOnlyResourceLoader() noexcept
{
static ScopedResourceLoader loader{ GetLibraryResourceLoader().WithQualifier(L"language", L"en-US") };
return loader;
}
DataTemplate SearchResultTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/)
{
return SelectTemplateCore(item);
}
DataTemplate SearchResultTemplateSelector::SelectTemplateCore(const IInspectable& item)
{
if (const auto searchResultItem = item.try_as<FilteredSearchResult>())
{
if (!searchResultItem->SecondaryLabel().empty())
{
return ComplexTemplate();
}
return BasicTemplate();
}
return nullptr;
}
Editor::FilteredSearchResult FilteredSearchResult::CreateNoResultsItem(const winrt::hstring& query)
{
return winrt::make<FilteredSearchResult>(nullptr, nullptr, hstring{ fmt::format(fmt::runtime(std::wstring{ RS_(L"Search_NoResults") }), query) });
}
// Creates a FilteredSearchResult with the given search index entry and runtime object.
// The resulting search result will have a label like "<ProfileName>: <display text>" or "<ProfileName>".
// This is so that we can reuse the display text from the search index, but also add additional context to which runtime object this search result maps to.
Editor::FilteredSearchResult FilteredSearchResult::CreateRuntimeObjectItem(const LocalizedIndexEntry* searchIndexEntry, const Windows::Foundation::IInspectable& runtimeObj)
{
hstring runtimeObjLabel{};
hstring runtimeObjContext{};
if (const auto profileVM = runtimeObj.try_as<Editor::ProfileViewModel>())
{
// No runtimeObjContext: profile name and icon should be enough
runtimeObjLabel = profileVM.Name();
}
else if (const auto colorSchemeVM = runtimeObj.try_as<Editor::ColorSchemeViewModel>())
{
// No runtimeObjContext: scheme name and generic icon should be enough
runtimeObjLabel = colorSchemeVM.Name();
}
else if (const auto ntmFolderEntryVM = runtimeObj.try_as<Editor::FolderEntryViewModel>())
{
runtimeObjLabel = ntmFolderEntryVM.Name();
runtimeObjContext = RS_(L"Nav_NewTabMenu/Content");
}
else if (const auto extensionPackageVM = runtimeObj.try_as<Editor::ExtensionPackageViewModel>())
{
runtimeObjLabel = extensionPackageVM.DisplayName();
runtimeObjContext = RS_(L"Nav_Extensions/Content");
}
else if (const auto commandVM = runtimeObj.try_as<Editor::CommandViewModel>())
{
runtimeObjLabel = commandVM.DisplayName();
runtimeObjContext = RS_(L"Nav_Actions/Content");
}
if (const auto& displayText = searchIndexEntry->Entry->DisplayTextLocalized; !displayText.empty())
{
// Full index entry (for settings within runtime objects)
// - primaryText: <displayText>
// - secondaryText: <runtimeObjLabel>
// navigates to setting container
return winrt::make<FilteredSearchResult>(searchIndexEntry, runtimeObj, displayText, runtimeObjLabel);
}
// Partial index entry (for runtime object main pages)
// - primaryText: <runtimeObjLabel>
// - secondaryText: <runtimeObjContext>
// "PowerShell" --> navigates to main runtime object page (i.e. Profiles_Base)
// "SSH" | "Extension" --> navigates to main runtime object page (i.e. Extensions > SSH)
return winrt::make<FilteredSearchResult>(searchIndexEntry, runtimeObj, runtimeObjLabel, runtimeObjContext);
}
winrt::hstring FilteredSearchResult::Label() const
{
if (_overrideLabel)
{
return *_overrideLabel;
}
return _SearchIndexEntry->Entry->DisplayTextLocalized;
}
bool FilteredSearchResult::IsNoResultsPlaceholder() const
{
return _overrideLabel.has_value() && !_NavigationArgOverride;
}
Windows::Foundation::IInspectable FilteredSearchResult::NavigationArg() const
{
if (_NavigationArgOverride)
{
return _NavigationArgOverride;
}
else if (_SearchIndexEntry)
{
return _SearchIndexEntry->Entry->NavigationArg;
}
return nullptr;
}
Windows::UI::Xaml::Controls::IconElement FilteredSearchResult::Icon() const
{
// We need to set the icon size here as opposed to in the XAML.
// Setting it in the XAML just crops the icon.
static constexpr double iconSize = 16;
if (auto navigationArg = NavigationArg())
{
if (const auto profileVM = navigationArg.try_as<Editor::ProfileViewModel>())
{
auto icon = UI::IconPathConverter::IconWUX(profileVM.EvaluatedIcon());
icon.Width(iconSize);
icon.Height(iconSize);
return icon;
}
else if (const auto colorSchemeVM = navigationArg.try_as<Editor::ColorSchemeViewModel>())
{
WUX::Controls::FontIcon icon{};
icon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(iconSize);
icon.Glyph(NavTagIconMap[colorSchemesTag]);
return icon;
}
else if (const auto ntmFolderEntryVM = navigationArg.try_as<Editor::FolderEntryViewModel>())
{
auto icon = UI::IconPathConverter::IconWUX(ntmFolderEntryVM.Icon());
icon.Width(iconSize);
icon.Height(iconSize);
return icon;
}
else if (const auto extensionPackageVM = navigationArg.try_as<Editor::ExtensionPackageViewModel>())
{
// TODO GH #19806: IconWUX() sets a size on the icon automatically. This is great
// for most icons, but font icons end up being a weird size.
// Check if we're using the generic font icon, and, if so, just build it ourselves
// so that it looks right.
const auto& extPkgVMIconPath = extensionPackageVM.Icon();
if (extPkgVMIconPath == NavTagIconMap[extensionsTag])
{
WUX::Controls::FontIcon icon{};
icon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(iconSize);
icon.Glyph(NavTagIconMap[extensionsTag]);
return icon;
}
auto icon = UI::IconPathConverter::IconWUX(extPkgVMIconPath);
icon.Width(iconSize);
icon.Height(iconSize);
return icon;
}
else if (const auto commandVM = navigationArg.try_as<Editor::CommandViewModel>())
{
WUX::Controls::FontIcon icon{};
icon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(iconSize);
icon.Glyph(NavTagIconMap[actionsTag]);
return icon;
}
else if (const auto stringNavArg = navigationArg.try_as<hstring>())
{
WUX::Controls::FontIcon icon{};
icon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(iconSize);
if (_SearchIndexEntry->Entry->SubPage == BreadcrumbSubPage::ColorSchemes_Edit)
{
// If we're editing a color scheme, stringNavArg is the color scheme name.
// Use the color scheme icon.
icon.Glyph(NavTagIconMap[colorSchemesTag]);
return icon;
}
else if (const auto it = NavTagIconMap.find(*stringNavArg); it != NavTagIconMap.end())
{
// Use the font icon used by the navigation view item
icon.Glyph(it->second);
return icon;
}
}
}
return nullptr;
}
// Reset the search index to the build-time data from GeneratedSettingsIndex.g.h
void SearchIndex::Reset()
{
// copied from CommandPaletteItems.h
static bool shouldIncludeLanguageNeutralResources = [] {
try
{
const auto context{ winrt::Windows::ApplicationModel::Resources::Core::ResourceContext::GetForViewIndependentUse() };
const auto qualifiers{ context.QualifierValues() };
if (const auto language{ qualifiers.TryLookup(L"language") })
{
return !til::starts_with_insensitive_ascii(*language, L"en-");
}
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
return false;
}();
// Creates the LocalizedIndexEntry wrapper objects around the given index entries
// and adds them to the given storage vector.
auto loadLocalizedIndex = [&](const auto& indexRef) {
std::vector<LocalizedIndexEntry> localizedIndex;
localizedIndex.reserve(indexRef.size());
for (const auto& entry : indexRef)
{
LocalizedIndexEntry localizedEntry;
localizedEntry.Entry = &entry;
if (shouldIncludeLanguageNeutralResources)
{
localizedEntry.DisplayTextNeutral = EnglishOnlyResourceLoader().GetLocalizedString(entry.DisplayTextUid);
}
localizedIndex.emplace_back(std::move(localizedEntry));
}
return localizedIndex;
};
IndexData indexData{
.mainIndex = loadLocalizedIndex(LoadBuildTimeIndex()),
.profileIndex = loadLocalizedIndex(LoadProfileIndex()),
.ntmFolderIndex = loadLocalizedIndex(LoadNTMFolderIndex()),
.colorSchemeIndex = loadLocalizedIndex(LoadColorSchemeIndex())
};
// Load partial entries for runtime object main pages
// (i.e. PowerShell profile --> PartialProfileIndexEntry() --> Profile page w/ PowerShell profile VM as context)
indexData.profileIndexEntry.Entry = &PartialProfileIndexEntry();
indexData.ntmFolderIndexEntry.Entry = &PartialNTMFolderIndexEntry();
indexData.extensionIndexEntry.Entry = &PartialExtensionIndexEntry();
indexData.colorSchemeIndexEntry.Entry = &PartialColorSchemeIndexEntry();
indexData.actionIndexEntry.Entry = &PartialActionIndexEntry();
_index = std::move(indexData);
}
// Method Description:
// - Gets the search results based on the given query string. Can be cancelled.
// - Some results (i.e. profiles) are dynamically generated at runtime, so they need to be passed in here so that we can include them.
// - LOAD-BEARING: vector views CANNOT be passed by reference. We need the AddRef to avoid lifetime issues with the async operation.
// Arguments:
// - query - the search query string
// - profileVMs - the list of profile view models to search
// - ntmFolderVMs - the list of New Tab Menu folder entry view models to search
// - colorSchemeVMs - the list of color scheme view models to search
// - extensionPkgVMs - the list of extension package view models to search
// - commandVMs - the list of command view models to search
// Return value:
// - The results are sorted by score (best matches first).
// - If no results are found, a "no results" placeholder item is returned.
IAsyncOperation<IObservableVector<Windows::Foundation::IInspectable>> SearchIndex::SearchAsync(const hstring& query,
const IVectorView<Editor::ProfileViewModel> profileVMs,
const IVectorView<Editor::FolderEntryViewModel> ntmFolderVMs,
const IVectorView<Editor::ColorSchemeViewModel> colorSchemeVMs,
const IVectorView<Editor::ExtensionPackageViewModel> extensionPkgVMs,
const IVectorView<Editor::CommandViewModel> commandVMs)
{
co_await winrt::resume_background();
// The search can be cancelled at any time by the caller.
// Get a token to check for cancellation as we go.
auto cancellationToken = co_await get_cancellation_token();
const auto filter = fzf::matcher::ParsePattern(std::wstring_view{ query });
std::vector<std::pair<int, Editor::FilteredSearchResult>> scoredResults;
for (const auto& entry : _index.mainIndex)
{
if (cancellationToken())
{
break;
}
// Calculate best score across all searchable fields for entry
int bestScore = 0;
for (const auto& [searchTextOpt, weight] : entry.GetSearchableFields())
{
if (searchTextOpt.has_value())
{
if (const auto match = fzf::matcher::Match(searchTextOpt.value(), filter))
{
bestScore = std::max(bestScore, match->Score * weight);
}
}
}
if (bestScore >= MinimumMatchScore)
{
scoredResults.emplace_back(bestScore, winrt::make<FilteredSearchResult>(&entry));
}
}
// Method Description:
// - Filter and score index dependent on runtime objects (i.e. profiles)
// - Matches against combined text: "<searchable field> <runtime object name>"
// - This allows queries like "font size powershell" to find "PowerShell: Font size"
// Arguments:
// - runtimeObjectList: the list of runtime objects to search (i.e. profiles, ntm folders, extensions, etc...)
// - searchIndex: the corresponding localized search index for the runtime object type (i.e. profile -> _index.profileIndex)
// - partialSearchIndexEntry: index entry that is populated with the runtime object (i.e. profile -> _index.profileIndexEntry)
auto appendRuntimeObjectResults = [&](const auto& runtimeObjectList, const std::vector<LocalizedIndexEntry>& searchIndex, const auto& partialSearchIndexEntry) {
for (const auto& runtimeObj : runtimeObjectList)
{
if (cancellationToken())
{
break;
}
// Check for direct runtime object name match first
const auto& objName = runtimeObj.Name();
const auto objNameMatch = fzf::matcher::Match(objName, filter);
if (objNameMatch && objNameMatch->Score >= MinimumMatchScore)
{
// navigates to runtime object main page (i.e. "PowerShell" Profiles_Base page)
scoredResults.emplace_back(objNameMatch->Score * WeightRuntimeObjectMatch,
FilteredSearchResult::CreateRuntimeObjectItem(&partialSearchIndexEntry, runtimeObj));
}
for (const auto& entry : searchIndex)
{
if (cancellationToken())
{
break;
}
// Calculate best score across all searchable fields for entry
int bestScore = 0;
for (const auto& [searchTextOpt, _] : entry.GetSearchableFields())
{
if (searchTextOpt.has_value())
{
// Score for combined text: "<searchable field> <runtime object name>"
const auto combinedText = fmt::format(L"{} {}", std::wstring_view{ searchTextOpt.value() }, std::wstring_view{ objName });
const auto combinedMatch = fzf::matcher::Match(combinedText, filter);
if (!combinedMatch)
{
continue;
}
const auto settingMatch = fzf::matcher::Match(searchTextOpt.value(), filter);
// Scoring:
// 1. require EITHER the runtime object OR the setting to match the query independently,
// OR the combined match scores very well (handles "font size powershell" where neither matches alone)
// 2. only include if query matches combined text (i.e. "font size PowerShell" matches "<setting name> <profile name>")
// 3. combined match scores higher than runtime object alone (setting must contribute to the match)
// NOTE: don't compare to settingMatch! This allows "font size" to show results for all profiles
const auto hasIndependentMatch = objNameMatch || settingMatch;
const auto hasStrongCombinedMatch = combinedMatch->Score >= MinimumMatchScore * 3;
if (!hasIndependentMatch && !hasStrongCombinedMatch)
{
continue;
}
if (combinedMatch->Score > (objNameMatch ? objNameMatch->Score : 0))
{
bestScore = std::max(combinedMatch->Score,
settingMatch ? settingMatch->Score : 0);
}
}
}
if (bestScore >= MinimumMatchScore)
{
// navigates to runtime object's setting (i.e. "PowerShell: Command line")
scoredResults.emplace_back(bestScore * WeightRuntimeObjectSetting,
FilteredSearchResult::CreateRuntimeObjectItem(&entry, runtimeObj));
}
}
}
};
appendRuntimeObjectResults(profileVMs, _index.profileIndex, _index.profileIndexEntry);
appendRuntimeObjectResults(ntmFolderVMs, _index.ntmFolderIndex, _index.ntmFolderIndexEntry);
appendRuntimeObjectResults(colorSchemeVMs, _index.colorSchemeIndex, _index.colorSchemeIndexEntry);
// Simple runtime object matching (no associated search index, just match by display name)
auto appendSimpleRuntimeObjectResults = [&](const auto& runtimeObjectList, const LocalizedIndexEntry& indexEntry, auto getDisplayName) {
for (const auto& runtimeObj : runtimeObjectList)
{
if (cancellationToken())
{
break;
}
if (const auto match = fzf::matcher::Match(getDisplayName(runtimeObj), filter); match && match->Score >= MinimumMatchScore)
{
// navigates to runtime object page (i.e. "Copy Text" --> Actions > EditAction page)
scoredResults.emplace_back(match->Score * WeightRuntimeObjectMatch,
FilteredSearchResult::CreateRuntimeObjectItem(&indexEntry, runtimeObj));
}
}
};
appendSimpleRuntimeObjectResults(extensionPkgVMs, _index.extensionIndexEntry, [](const auto& ext) { return ext.DisplayName(); });
appendSimpleRuntimeObjectResults(commandVMs, _index.actionIndexEntry, [](const auto& cmd) { return cmd.DisplayName(); });
// must be IInspectable to be used as ItemsSource in XAML
std::vector<Windows::Foundation::IInspectable> results;
if (scoredResults.empty())
{
// Explicitly show "no results"
results.reserve(1);
results.emplace_back(FilteredSearchResult::CreateNoResultsItem(query));
}
else
{
// Sort results by score (descending)
results.reserve(scoredResults.size());
std::sort(scoredResults.begin(), scoredResults.end(), [](const auto& a, const auto& b) { return a.first > b.first; });
std::transform(scoredResults.begin(), scoredResults.end(), std::back_inserter(results), [](const auto& pair) { return pair.second; });
}
if (cancellationToken())
{
// Search was cancelled; do not return any results
co_return single_threaded_observable_vector<Windows::Foundation::IInspectable>();
}
co_return single_threaded_observable_vector(std::move(results));
}
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "FilteredSearchResult.g.h"
#include "SearchResultTemplateSelector.g.h"
#include "GeneratedSettingsIndex.g.h"
#include <til/generational.h>
#include "..\fzf\fzf.h"
class ScopedResourceLoader;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
const ScopedResourceLoader& EnglishOnlyResourceLoader() noexcept;
// Wrapper for IndexEntry objects.
// Adds neutral locale, if necessary.
struct LocalizedIndexEntry
{
std::optional<winrt::hstring> DisplayTextNeutral = std::nullopt;
const IndexEntry* Entry = nullptr;
std::array<std::pair<std::optional<winrt::hstring>, int>, 2> GetSearchableFields() const;
};
// WinRT object representing a single filtered search result
struct FilteredSearchResult : FilteredSearchResultT<FilteredSearchResult>
{
FilteredSearchResult(const LocalizedIndexEntry* entry, const Windows::Foundation::IInspectable& navigationArgOverride = nullptr, const std::optional<hstring>& label = std::nullopt, const hstring secondaryLabel = {}) :
_SearchIndexEntry{ entry },
_NavigationArgOverride{ navigationArgOverride },
_overrideLabel{ label },
_secondaryLabel{ secondaryLabel } {}
static Editor::FilteredSearchResult CreateNoResultsItem(const winrt::hstring& query);
static Editor::FilteredSearchResult CreateRuntimeObjectItem(const LocalizedIndexEntry* searchIndexEntry, const Windows::Foundation::IInspectable& runtimeObj);
hstring ToString() { return Label(); }
winrt::hstring Label() const;
winrt::hstring SecondaryLabel() const { return _secondaryLabel; };
bool IsNoResultsPlaceholder() const;
const LocalizedIndexEntry& SearchIndexEntry() const noexcept { return *_SearchIndexEntry; }
Windows::Foundation::IInspectable NavigationArg() const;
Windows::UI::Xaml::Controls::IconElement Icon() const;
private:
const std::optional<winrt::hstring> _overrideLabel{ std::nullopt };
const winrt::hstring _secondaryLabel{};
const Windows::Foundation::IInspectable _NavigationArgOverride{ nullptr };
const LocalizedIndexEntry* _SearchIndexEntry{ nullptr };
};
struct SearchResultTemplateSelector : SearchResultTemplateSelectorT<SearchResultTemplateSelector>
{
SearchResultTemplateSelector() = default;
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item);
til::property<winrt::Windows::UI::Xaml::DataTemplate> BasicTemplate;
til::property<winrt::Windows::UI::Xaml::DataTemplate> ComplexTemplate;
};
// The main search index implemented as a singleton.
// The index loads data generated by tools\GenerateSettingsIndex.ps1 (outputs "Generated Files\GeneratedSettingsIndex.g.h" and cpp).
// Outputs FilteredSearchResult objects for UI display. UI is determined by SearchResultTemplateSelector.
class SearchIndex final
{
public:
static SearchIndex& Instance() noexcept
{
static SearchIndex instance;
return instance;
}
SearchIndex(const SearchIndex&) = delete;
SearchIndex& operator=(const SearchIndex&) = delete;
SearchIndex(SearchIndex&&) = delete;
SearchIndex& operator=(SearchIndex&&) = delete;
void Reset();
Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable>> SearchAsync(const winrt::hstring& query,
const Windows::Foundation::Collections::IVectorView<Editor::ProfileViewModel> profileVMs,
const Windows::Foundation::Collections::IVectorView<Editor::FolderEntryViewModel> ntmFolderVMs,
const Windows::Foundation::Collections::IVectorView<Editor::ColorSchemeViewModel> colorSchemeVMs,
const Windows::Foundation::Collections::IVectorView<Editor::ExtensionPackageViewModel> extensionPkgVMs,
const Windows::Foundation::Collections::IVectorView<Editor::CommandViewModel> commandVMs);
private:
SearchIndex() = default;
struct IndexData
{
IndexData& operator=(const IndexData& other) = default;
// Basic index data loaded from GeneratedSettingsIndex.g.h and wrapped as LocalizedIndexEntry objects.
// Non-main indices are duplicated at runtime when searching for runtime objects.
std::vector<LocalizedIndexEntry> mainIndex;
std::vector<LocalizedIndexEntry> profileIndex;
std::vector<LocalizedIndexEntry> ntmFolderIndex;
std::vector<LocalizedIndexEntry> colorSchemeIndex;
// Links to main page; used when searching runtime objects
LocalizedIndexEntry profileIndexEntry;
LocalizedIndexEntry ntmFolderIndexEntry;
LocalizedIndexEntry colorSchemeIndexEntry;
LocalizedIndexEntry extensionIndexEntry;
LocalizedIndexEntry actionIndexEntry;
} _index;
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(SearchResultTemplateSelector);
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "MainPage.idl";
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass FilteredSearchResult : Windows.Foundation.IStringable
{
String Label { get; };
String SecondaryLabel { get; };
Windows.UI.Xaml.Controls.IconElement Icon { get; };
}
[default_interface] runtimeclass SearchResultTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
{
SearchResultTemplateSelector();
Windows.UI.Xaml.DataTemplate BasicTemplate;
Windows.UI.Xaml.DataTemplate ComplexTemplate;
}
}

View File

@@ -3,6 +3,8 @@
#pragma once
#include "SettingContainer.h"
// This macro must be used alongside GETSET_BINDABLE_ENUM_SETTING.
// Use this in your class's constructor after Initialize_Component().
// It sorts and initializes the observable list of enum entries with the enum name
@@ -116,4 +118,32 @@ struct HasScrollViewer
DismissAllPopups(uielem.XamlRoot());
}
}
// Finds the element with the given name and brings it into view
void BringIntoViewWhenLoaded(const winrt::hstring elementName)
{
if (elementName.empty())
{
return;
}
auto* pThis = static_cast<T*>(this);
_loadedRevoker = pThis->Loaded(winrt::auto_revoke, [weakThis{ pThis->get_weak() }, elementName](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
if (const auto& controlToFocus{ page->FindName(elementName).try_as<winrt::Windows::UI::Xaml::Controls::Control>() })
{
// We need to wait for the page to be loaded
// or else the call to StartBringIntoView()
// will end up doing nothing
controlToFocus.StartBringIntoView();
controlToFocus.Focus(winrt::Windows::UI::Xaml::FocusState::Programmatic);
}
page->_loadedRevoker.revoke();
}
});
}
protected:
winrt::Windows::UI::Xaml::FrameworkElement::Loaded_revoker _loadedRevoker;
};

View File

@@ -59,9 +59,8 @@ namespace winrt
namespace WARC = ::winrt::Windows::ApplicationModel::Resources::Core;
}
// Like RS_ and RS_fmt, but they use an ambient boolean named "localized" to
// determine whether to load the English version of a resource or the localized
// one.
// Like RS_ and RS_fmt, but they use an ambient context to determine
// whether to load the English version of a resource or the localized one.
#define RS_switchable_(x) RS_switchable_impl(context, USES_RESOURCE(x))
#define RS_switchable_fmt(x, ...) RS_switchable_fmt_impl(context, USES_RESOURCE(x), __VA_ARGS__)

View File

@@ -103,6 +103,7 @@ Author(s):
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \
X(bool, ForceVTInput, "compatibility.input.forceVT", false) \
X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \
X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \

View File

@@ -88,6 +88,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
INHERITABLE_PROFILE_SETTING(Boolean, RainbowSuggestions);
INHERITABLE_PROFILE_SETTING(Boolean, ForceVTInput);
INHERITABLE_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
INHERITABLE_PROFILE_SETTING(Boolean, AllowKeypadMode);
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);

View File

@@ -49,6 +49,7 @@
X(bool, TrimBlockSelection, true) \
X(bool, SuppressApplicationTitle) \
X(bool, ForceVTInput, false) \
X(bool, AllowKittyKeyboardMode, true) \
X(winrt::hstring, StartingTitle) \
X(bool, DetectURLs, true) \
X(bool, AutoMarkPrompts) \

View File

@@ -244,6 +244,39 @@ private:
static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property;
#endif
#ifndef ATTACHED_DEPENDENCY_PROPERTY
#define ATTACHED_DEPENDENCY_PROPERTY(type, name) \
public: \
static winrt::Windows::UI::Xaml::DependencyProperty name##Property() \
{ \
return _##name##Property; \
} \
static type Get##name(winrt::Windows::UI::Xaml::DependencyObject const& target) \
{ \
auto&& temp{ target.GetValue(_##name##Property) }; \
if (temp) \
{ \
return winrt::unbox_value<type>(temp); \
} \
\
if constexpr (std::is_base_of_v<winrt::Windows::Foundation::IInspectable, type>) \
{ \
return { nullptr }; \
} \
else \
{ \
return {}; \
} \
} \
static void Set##name(winrt::Windows::UI::Xaml::DependencyObject const& target, const type& value) \
{ \
target.SetValue(_##name##Property, winrt::box_value(value)); \
} \
\
private: \
static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property;
#endif
// Use this macro for quickly defining the factory_implementation part of a
// class. CppWinrt requires these for the compiler, but more often than not,
// they require no customization. See

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "..\TerminalApp\fzf\fzf.h"
#include "..\fzf\fzf.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;

View File

@@ -1151,6 +1151,8 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
const size_t col2 = _api.bufferLineColumn[a.textPosition + i];
const auto fg = colors[col1 << shift];
// TODO: Instead of aligning each DWrite-cluster to the cell grid,
// we should align each grapheme cluster to the cell grid.
const auto expectedAdvance = (col2 - col1) * _p.s->font->cellSize.x;
f32 actualAdvance = 0;
for (auto j = prevCluster; j < nextCluster; ++j)

View File

@@ -67,6 +67,10 @@ public:
virtual void DeleteColumn(const VTInt distance) = 0; // DECDC
virtual void SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
virtual void SetAnsiMode(const bool ansiMode) = 0; // DECANM
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) = 0; // CSI = flags ; mode u
virtual void QueryKittyKeyboardProtocol() = 0; // CSI ? u
virtual void PushKittyKeyboardProtocol(const VTParameter flags) = 0; // CSI > flags u
virtual void PopKittyKeyboardProtocol(const VTParameter count) = 0; // CSI < count u
virtual void SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
virtual void SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
virtual void EnquireAnswerback() = 0; // ENQ

View File

@@ -2054,6 +2054,35 @@ void AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
_terminalInput.SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
}
// CSI = flags ; mode u - Sets kitty keyboard protocol flags
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode)
{
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
const auto KittyKeyboardProtocol = static_cast<TerminalInput::KittyKeyboardProtocolMode>(mode.value_or(1));
_terminalInput.SetKittyKeyboardProtocol(kittyFlags, KittyKeyboardProtocol);
}
// CSI ? u - Queries current kitty keyboard protocol flags
void AdaptDispatch::QueryKittyKeyboardProtocol()
{
const auto flags = static_cast<VTInt>(_terminalInput.GetKittyFlags());
_ReturnCsiResponse(fmt::format(FMT_COMPILE(L"?{}u"), flags));
}
// CSI > flags u - Pushes current kitty keyboard flags onto the stack and sets new flags
void AdaptDispatch::PushKittyKeyboardProtocol(const VTParameter flags)
{
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
_terminalInput.PushKittyFlags(kittyFlags);
}
// CSI < count u - Pops one or more entries from the kitty keyboard stack
void AdaptDispatch::PopKittyKeyboardProtocol(const VTParameter count)
{
const auto popCount = static_cast<size_t>(count.value_or(1));
_terminalInput.PopKittyFlags(popCount);
}
// Routine Description:
// - Internal logic for adding or removing lines in the active screen buffer.
// This also moves the cursor to the left margin, which is expected behavior for IL and DL.

View File

@@ -97,6 +97,10 @@ namespace Microsoft::Console::VirtualTerminal
void RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM
void SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
void SetAnsiMode(const bool ansiMode) override; // DECANM
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) override; // Kitty keyboard protocol CSI = flags ; mode u
void QueryKittyKeyboardProtocol() override; // Kitty keyboard protocol CSI ? u
void PushKittyKeyboardProtocol(const VTParameter flags) override; // Kitty keyboard protocol CSI > flags u
void PopKittyKeyboardProtocol(const VTParameter count) override; // Kitty keyboard protocol CSI < count u
void SetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin) override; // DECSTBM
void SetLeftRightScrollingMargins(const VTInt leftMargin,

View File

@@ -54,6 +54,10 @@ public:
void DeleteColumn(const VTInt /*distance*/) override {} // DECDC
void SetKeypadMode(const bool /*applicationMode*/) override {} // DECKPAM, DECKPNM
void SetAnsiMode(const bool /*ansiMode*/) override {} // DECANM
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) override {} // CSI = flags ; mode u
void QueryKittyKeyboardProtocol() override {} // CSI ? u
void PushKittyKeyboardProtocol(const VTParameter /*flags*/) override {} // CSI > flags u
void PopKittyKeyboardProtocol(const VTParameter /*count*/) override {} // CSI < count u
void SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override {} // DECSTBM
void SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override {} // DECSLRM
void EnquireAnswerback() override {} // ENQ

View File

@@ -12,7 +12,6 @@
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="..\mouseInput.cpp" />
<ClCompile Include="..\mouseInputState.cpp" />
<ClCompile Include="..\terminalInput.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View File

@@ -487,7 +487,7 @@ TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point p
// True if the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
{
const auto inAltBuffer{ _mouseInputState.inAlternateBuffer };
const auto inAltBuffer{ _inAlternateBuffer };
const auto inAltScroll{ _inputMode.test(Mode::AlternateScroll) };
const auto wasMouseWheel{ (button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0 };
return inAltBuffer && inAltScroll && wasMouseWheel;

View File

@@ -1,30 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <windows.h>
#include "terminalInput.hpp"
using namespace Microsoft::Console::VirtualTerminal;
// Routine Description:
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
// Parameters:
// <none>
// Return value:
// <none>
void TerminalInput::UseAlternateScreenBuffer() noexcept
{
_mouseInputState.inAlternateBuffer = true;
}
// Routine Description:
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
// Parameters:
// <none>
// Return value:
// <none>
void TerminalInput::UseMainScreenBuffer() noexcept
{
_mouseInputState.inAlternateBuffer = false;
}

View File

@@ -30,7 +30,6 @@ PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES= \
..\terminalInput.cpp \
..\mouseInput.cpp \
..\mouseInputState.cpp \
INCLUDES = \
$(INCLUDES); \

View File

@@ -6,8 +6,6 @@
#include <til/unicode.h>
#include "../../inc/unicode.hpp"
#include "../../interactivity/inc/VtApiRedirection.hpp"
#include "../types/inc/IInputEvent.hpp"
using namespace std::string_literals;
@@ -31,6 +29,16 @@ TerminalInput::TerminalInput() noexcept
_initKeyboardMap();
}
void TerminalInput::UseAlternateScreenBuffer() noexcept
{
_inAlternateBuffer = true;
}
void TerminalInput::UseMainScreenBuffer() noexcept
{
_inAlternateBuffer = false;
}
void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept
{
// If we're changing a tracking mode, we always clear other tracking modes first.
@@ -70,6 +78,7 @@ void TerminalInput::ResetInputModes() noexcept
_inputMode = { Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll };
_mouseInputState.lastPos = { -1, -1 };
_mouseInputState.lastButton = 0;
ResetKittyKeyboardProtocols();
_initKeyboardMap();
}
@@ -78,6 +87,86 @@ void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexce
_forceDisableWin32InputMode = win32InputMode;
}
void TerminalInput::ForceDisableKittyKeyboardProtocol(const bool disable) noexcept
{
_forceDisableKittyKeyboardProtocol = disable;
if (disable)
{
_kittyFlags = 0;
}
}
// Kitty keyboard protocol methods
void TerminalInput::SetKittyKeyboardProtocol(const uint8_t flags, const KittyKeyboardProtocolMode mode) noexcept
{
if (_forceDisableKittyKeyboardProtocol)
{
return;
}
switch (mode)
{
case KittyKeyboardProtocolMode::Replace:
_kittyFlags = flags & KittyKeyboardProtocolFlags::All;
break;
case KittyKeyboardProtocolMode::Set:
_kittyFlags |= (flags & KittyKeyboardProtocolFlags::All);
break;
case KittyKeyboardProtocolMode::Reset:
_kittyFlags &= ~(flags & KittyKeyboardProtocolFlags::All);
break;
}
}
uint8_t TerminalInput::GetKittyFlags() const noexcept
{
return _kittyFlags;
}
void TerminalInput::PushKittyFlags(const uint8_t flags) noexcept
{
if (_forceDisableKittyKeyboardProtocol)
{
return;
}
auto& stack = _getKittyStack();
// Evict oldest entry if stack is full (DoS prevention)
if (stack.size() >= KittyStackMaxSize)
{
stack.erase(stack.begin());
}
stack.push_back(_kittyFlags);
_kittyFlags = flags & KittyKeyboardProtocolFlags::All;
}
void TerminalInput::PopKittyFlags(const size_t count) noexcept
{
auto& stack = _getKittyStack();
// If pop request exceeds stack size, reset all flags per spec:
// "If a pop request is received that empties the stack, all flags are reset."
if (count > stack.size())
{
stack.clear();
_kittyFlags = 0;
return;
}
// Pop the requested number of entries, restoring flags from last popped
for (size_t i = 0; i < count; ++i)
{
_kittyFlags = stack.back();
stack.pop_back();
}
}
void TerminalInput::ResetKittyKeyboardProtocols() noexcept
{
_kittyFlags = 0;
_kittyMainStack.clear();
_kittyAltStack.clear();
}
TerminalInput::OutputType TerminalInput::MakeUnhandled() noexcept
{
return {};
@@ -121,12 +210,20 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
}
const auto controlKeyState = _trackControlKeyState(keyEvent);
const auto virtualKeyCode = keyEvent.wVirtualKeyCode;
auto unicodeChar = keyEvent.uChar.UnicodeChar;
// Check if this key matches the last recorded key code.
const auto matchingLastKeyPress = _lastVirtualKeyCode == virtualKeyCode;
// If kitty keyboard mode is active, use kitty keyboard protocol.
// This handles release events when ReportEventTypes flag is set.
if (_kittyFlags != 0)
{
return _makeKittyOutput(keyEvent, controlKeyState);
}
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
if (!keyEvent.bKeyDown)
{
@@ -669,3 +766,617 @@ TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
return fmt::format(FMT_COMPILE(L"{}{};{};{};{};{};{}_"), _csi, vk, sc, uc, kd, cs, rc);
}
// Generates kitty keyboard protocol output for a key event.
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
TerminalInput::OutputType TerminalInput::_makeKittyOutput(const KEY_EVENT_RECORD& key, const DWORD controlKeyState)
{
const auto virtualKeyCode = key.wVirtualKeyCode;
const auto virtualScanCode = key.wVirtualScanCode;
const auto unicodeChar = key.uChar.UnicodeChar;
const auto isKeyDown = key.bKeyDown;
// Swallow lone leading surrogates...
if (til::is_leading_surrogate(unicodeChar))
{
_leadingSurrogate = unicodeChar;
return _makeNoOutput();
}
// ...and combine them with trailing surrogates.
uint32_t fullCodepoint = unicodeChar;
if (_leadingSurrogate != 0 && til::is_trailing_surrogate(unicodeChar))
{
fullCodepoint = til::combine_surrogates(_leadingSurrogate, unicodeChar);
_leadingSurrogate = 0;
}
else
{
_leadingSurrogate = 0;
}
// Check if this key matches the last recorded key code (for repeat detection)
const auto isRepeat = _lastVirtualKeyCode == virtualKeyCode && isKeyDown;
if (!isKeyDown)
{
if (_lastVirtualKeyCode == virtualKeyCode)
{
_lastVirtualKeyCode = std::nullopt;
}
}
else
{
_lastVirtualKeyCode = virtualKeyCode;
}
// Note: Disambiguate flag (0x01) is implicitly handled - if we're in this function
// at all (_kittyFlags != 0), then Ctrl+key and Alt+key combos get CSI u encoding.
const auto reportEventTypes = (_kittyFlags & KittyKeyboardProtocolFlags::ReportEventTypes) != 0;
const auto reportAllKeys = (_kittyFlags & KittyKeyboardProtocolFlags::ReportAllKeys) != 0;
const auto reportAlternateKeys = (_kittyFlags & KittyKeyboardProtocolFlags::ReportAlternateKeys) != 0;
const auto reportText = (_kittyFlags & KittyKeyboardProtocolFlags::ReportText) != 0;
// Without ReportEventTypes, we only handle key down events
if (!isKeyDown && !reportEventTypes)
{
return _makeNoOutput();
}
// Get the functional key code, or 0 if this key should use legacy encoding.
const auto functionalKeyCode = _getKittyFunctionalKeyCode(virtualKeyCode, virtualScanCode, controlKeyState);
const auto ctrlIsPressed = WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED);
const auto altIsPressed = WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED);
if (!reportAllKeys)
{
// Per spec: "Additionally, with this mode [ReportAllKeys], events for pressing
// modifier keys are reported." So we skip modifier key events without it.
if ((functionalKeyCode >= 57358 && functionalKeyCode <= 57360) ||
(functionalKeyCode >= 57441 && functionalKeyCode <= 57450))
{
return _makeNoOutput();
}
// Legacy encoding for Enter, Tab, and Backspace (spec recovery guarantee).
// These keys use mode-aware legacy sequences unless ReportAllKeys is set, ensuring
// users can type "reset<Enter>" if an app crashes with the protocol enabled.
// Unlike CSI u (which is mode-independent), legacy encoding must honor LineFeed
// and BackarrowKey modes. Ctrl/Alt combos still use CSI u for disambiguation.
if (virtualKeyCode == VK_RETURN || virtualKeyCode == VK_TAB || virtualKeyCode == VK_BACK)
{
if (!isKeyDown || ctrlIsPressed || altIsPressed)
{
return _makeNoOutput();
}
std::wstring str;
switch (virtualKeyCode)
{
case VK_RETURN:
str = _inputMode.test(Mode::LineFeed) ? L"\r\n" : L"\r";
break;
case VK_TAB:
if (WI_IsFlagSet(controlKeyState, SHIFT_PRESSED))
{
str = fmt::format(FMT_COMPILE(L"{}Z"), _csi);
}
else
{
str = L"\t";
}
break;
case VK_BACK:
str = _inputMode.test(Mode::BackarrowKey) ? L"\x08" : L"\x7f";
break;
default:
break;
}
return MakeOutput(std::move(str));
}
// Fast path: For simple text key presses (key down, not a functional key, has a codepoint),
// without Ctrl/Alt modifiers that require disambiguation, and not in reportAllKeys mode,
// we can bypass CSI u encoding and send the character directly.
if (isKeyDown && functionalKeyCode == 0 && fullCodepoint != 0 && !ctrlIsPressed && !altIsPressed)
{
const auto cb = _codepointToBuffer(fullCodepoint);
return MakeOutput({ cb.buf, cb.len });
}
}
const auto isEnhanced = WI_IsFlagSet(controlKeyState, ENHANCED_KEY);
wchar_t legacyFinalChar = 0;
uint32_t legacyParam = 1;
switch (virtualKeyCode)
{
case VK_UP:
if (isEnhanced)
{
legacyFinalChar = L'A';
}
break;
case VK_DOWN:
if (isEnhanced)
{
legacyFinalChar = L'B';
}
break;
case VK_RIGHT:
if (isEnhanced)
{
legacyFinalChar = L'C';
}
break;
case VK_LEFT:
if (isEnhanced)
{
legacyFinalChar = L'D';
}
break;
case VK_HOME:
if (isEnhanced)
{
legacyFinalChar = L'H';
}
break;
case VK_END:
if (isEnhanced)
{
legacyFinalChar = L'F';
}
break;
case VK_INSERT:
case VK_DELETE:
if (isEnhanced)
{
legacyFinalChar = L'~';
legacyParam = 2 + (virtualKeyCode - VK_INSERT);
}
break;
case VK_PRIOR:
case VK_NEXT:
if (isEnhanced)
{
legacyFinalChar = L'~';
legacyParam = 5 + (virtualKeyCode - VK_PRIOR);
}
break;
case VK_F1:
case VK_F2:
case VK_F4:
legacyFinalChar = L'P' + (virtualKeyCode - VK_F1);
break;
case VK_F3:
// Note: F3 cannot use CSI R as that conflicts with Cursor Position Report.
// The kitty spec explicitly removed CSI R for F3.
legacyFinalChar = L'~';
legacyParam = 13;
break;
case VK_F5:
legacyFinalChar = L'~';
legacyParam = 15;
break;
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
case VK_F10:
legacyFinalChar = L'~';
legacyParam = 17 + (virtualKeyCode - VK_F6);
break;
case VK_F11:
case VK_F12:
legacyFinalChar = L'~';
legacyParam = 23 + (virtualKeyCode - VK_F11);
break;
default:
break;
}
// Calculate kitty modifiers early - needed for legacy sequences too
// kitty: shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
uint32_t modifiers = 0;
if (WI_IsFlagSet(controlKeyState, SHIFT_PRESSED))
{
modifiers |= 1;
}
if (WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED))
{
modifiers |= 2;
}
if (WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED))
{
modifiers |= 4;
}
// Per spec: "Lock modifiers are not reported for text producing keys, to keep them
// usable in legacy programs. To get lock modifiers for all keys use the Report all
// keys as escape codes enhancement." So we report them for functional keys always,
// and for text-producing keys only when ReportAllKeys is set.
if (functionalKeyCode != 0 || reportAllKeys)
{
if (WI_IsFlagSet(controlKeyState, CAPSLOCK_ON))
{
modifiers |= 64;
}
if (WI_IsFlagSet(controlKeyState, NUMLOCK_ON))
{
modifiers |= 128;
}
}
const auto encodedModifiers = 1 + modifiers;
// Determine event type: 1=press, 2=repeat, 3=release
uint32_t eventType = 1;
if (!isKeyDown)
{
eventType = 3;
}
else if (isRepeat)
{
eventType = 2;
}
// If this is a key that uses legacy CSI sequences, generate it
if (legacyFinalChar != 0)
{
// Format: CSI param ; modifiers ~ or CSI param ; modifiers : event-type ~
std::wstring seq;
seq.append(_csi);
if (legacyParam > 1)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), legacyParam);
}
if (encodedModifiers > 1 || (reportEventTypes && eventType > 1))
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), encodedModifiers);
if (reportEventTypes && eventType > 1)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), eventType);
}
}
seq.push_back(legacyFinalChar);
return seq;
}
// According to kitty protocol:
// > the codepoint used is always the lower-case (or more technically, un-shifted) version of the key
uint32_t keyCode = functionalKeyCode;
if (keyCode == 0)
{
// For alphabetic keys, use the virtual key code converted to lowercase.
// We can't use unicodeChar because when Ctrl is pressed, unicodeChar
// becomes the control character (e.g., Ctrl+C gives unicodeChar=0x03).
if (virtualKeyCode >= 'A' && virtualKeyCode <= 'Z')
{
keyCode = virtualKeyCode + 32; // Convert to lowercase ('A'->'a')
}
// Space needs special handling because Ctrl+Space produces NUL (0).
else if (virtualKeyCode == VK_SPACE)
{
keyCode = L' ';
}
else
{
keyCode = fullCodepoint;
// For control characters (e.g., Ctrl+[ produces ESC), use ToUnicodeEx
// to get the base character without modifiers.
if (!_codepointIsNonControl(keyCode))
{
const auto hkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
auto keyState = _getKeyboardState(virtualKeyCode, 0);
// Disable Ctrl and Alt modifiers to obtain the base character mapping.
keyState.at(VK_CONTROL) = keyState.at(VK_LCONTROL) = keyState.at(VK_RCONTROL) = 0;
keyState.at(VK_MENU) = keyState.at(VK_LMENU) = keyState.at(VK_RMENU) = 0;
wchar_t buffer[4];
const auto result = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer, 4, 4, hkl);
if (result > 0 && result < 4)
{
keyCode = _bufferToCodepoint(&buffer[0]);
}
}
keyCode = _codepointToLower(keyCode);
if (!_codepointIsNonControl(keyCode))
{
return _makeNoOutput();
}
}
}
// Add alternate keys if requested (shifted key and base layout key)
uint32_t shiftedKey = 0;
uint32_t baseLayoutKey = 0;
if (reportAlternateKeys && functionalKeyCode == 0)
{
// Shifted key: the uppercase/shifted version of the key
if ((modifiers & 1) != 0 && fullCodepoint != 0 && fullCodepoint != keyCode)
{
shiftedKey = fullCodepoint;
}
// Base layout key: the key in the standard US PC-101 layout.
static const auto usLayout = LoadKeyboardLayoutW(L"00000409", 0);
if (usLayout != nullptr && virtualKeyCode != 0)
{
auto keyState = _getKeyboardState(virtualKeyCode, 0); // No modifiers for base key
wchar_t baseChar[4]{};
const auto result = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), baseChar, 4, 4, usLayout);
if (result == 1 && baseChar[0] >= 0x20)
{
// Use lowercase version of the base layout key
auto baseKey = static_cast<uint32_t>(baseChar[0]);
if (baseKey >= L'A' && baseKey <= L'Z')
{
baseKey += 32;
}
// Only include if different from keyCode
if (baseKey != keyCode)
{
baseLayoutKey = baseKey;
}
}
}
}
// CSI unicode-key-code:shifted-key:base-layout-key ; modifiers:event-type ; text-as-codepoints u
std::wstring seq;
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}{}"), _csi, keyCode);
// Append alternate keys to sequence if present
if (shiftedKey != 0 || baseLayoutKey != 0)
{
seq.push_back(L':');
if (shiftedKey != 0)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), shiftedKey);
}
if (baseLayoutKey != 0)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), baseLayoutKey);
}
}
// Determine if we need to output text-as-codepoints (third field)
// Exclude C0 (< 0x20) and C1 (0x80-0x9F) control codes per spec.
const auto isValidText = fullCodepoint >= 0x20 && (fullCodepoint < 0x80 || fullCodepoint > 0x9F);
const auto needsText = reportText && reportAllKeys && functionalKeyCode == 0 && isValidText && isKeyDown;
// We need to include modifiers field if:
// - modifiers are non-default (encodedModifiers > 1), OR
// - we need to report non-press event type, OR
// - we need to output text (text is the 3rd field, so we must have 2nd field too)
const auto needsEventType = reportEventTypes && eventType > 1;
if (encodedModifiers > 1 || needsEventType || needsText)
{
// Per spec: "If no modifiers are present, the modifiers field must have the value 1"
// when event type sub-field is needed.
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), encodedModifiers);
if (needsEventType)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), eventType);
}
if (needsText)
{
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), fullCodepoint);
}
}
seq.push_back(L'u');
return seq;
}
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
// NOTE: The definition documents keys named as KP_*, which are keypad keys.
uint32_t TerminalInput::_getKittyFunctionalKeyCode(const WORD virtualKeyCode, const WORD virtualScanCode, const DWORD controlKeyState) noexcept
{
const auto isEnhanced = WI_IsFlagSet(controlKeyState, ENHANCED_KEY);
switch (virtualKeyCode)
{
// Special keys with C0 control codes
case VK_ESCAPE:
return 27; // ESCAPE
case VK_RETURN:
return isEnhanced ? 57414 : 13; // KP_RETURN : ENTER
case VK_TAB:
return 9; // TAB
case VK_BACK:
return 127; // BACKSPACE
// Navigation keys - when ENHANCED_KEY is not set, these are keypad keys
case VK_INSERT:
return isEnhanced ? 0 : 57425; // legacy : KP_INSERT
case VK_DELETE:
return isEnhanced ? 0 : 57426; // legacy : KP_DELETE
case VK_LEFT:
return isEnhanced ? 0 : 57417; // legacy : KP_LEFT
case VK_RIGHT:
return isEnhanced ? 0 : 57418; // legacy : KP_RIGHT
case VK_UP:
return isEnhanced ? 0 : 57419; // legacy : KP_UP
case VK_DOWN:
return isEnhanced ? 0 : 57420; // legacy : KP_DOWN
case VK_PRIOR:
return isEnhanced ? 0 : 57421; // legacy : KP_PAGE_UP
case VK_NEXT:
return isEnhanced ? 0 : 57422; // legacy : KP_PAGE_DOWN
case VK_HOME:
return isEnhanced ? 0 : 57423; // legacy : KP_HOME
case VK_END:
return isEnhanced ? 0 : 57424; // legacy : KP_END
// Lock keys
case VK_CAPITAL:
return 57358; // CAPS_LOCK
case VK_SCROLL:
return 57359; // SCROLL_LOCK
case VK_NUMLOCK:
return 57360; // NUM_LOCK
// Other special keys
case VK_SNAPSHOT:
return 57361; // PRINT_SCREEN
case VK_PAUSE:
return 57362; // PAUSE
case VK_APPS:
return 57363; // MENU
// Function keys
case VK_F1:
case VK_F2:
case VK_F3:
case VK_F4:
case VK_F5:
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
case VK_F10:
case VK_F11:
case VK_F12:
return 0; // Use legacy sequences
case VK_F13:
case VK_F14:
case VK_F15:
case VK_F16:
case VK_F17:
case VK_F18:
case VK_F19:
case VK_F20:
case VK_F21:
case VK_F22:
case VK_F23:
case VK_F24:
return 57376 + (virtualKeyCode - VK_F13); // F13-F24
// Keypad keys
case VK_NUMPAD0:
case VK_NUMPAD1:
case VK_NUMPAD2:
case VK_NUMPAD3:
case VK_NUMPAD4:
case VK_NUMPAD5:
case VK_NUMPAD6:
case VK_NUMPAD7:
case VK_NUMPAD8:
case VK_NUMPAD9:
return 57399 + (virtualKeyCode - VK_NUMPAD0); // KP_0-KP_9
case VK_DECIMAL:
return 57409; // KP_DECIMAL
case VK_DIVIDE:
return 57410; // KP_DIVIDE
case VK_MULTIPLY:
return 57411; // KP_MULTIPLY
case VK_SUBTRACT:
return 57412; // KP_SUBTRACT
case VK_ADD:
return 57413; // KP_ADD
case VK_SEPARATOR:
return 57416; // KP_SEPARATOR
case VK_CLEAR:
return 57427; // KP_BEGIN
// Media keys
case VK_MEDIA_PLAY_PAUSE:
return 57430; // MEDIA_PLAY_PAUSE
case VK_MEDIA_STOP:
return 57432; // MEDIA_STOP
case VK_MEDIA_NEXT_TRACK:
return 57435; // MEDIA_TRACK_NEXT
case VK_MEDIA_PREV_TRACK:
return 57436; // MEDIA_TRACK_PREVIOUS
case VK_VOLUME_DOWN:
return 57438; // LOWER_VOLUME
case VK_VOLUME_UP:
return 57439; // RAISE_VOLUME
case VK_VOLUME_MUTE:
return 57440; // MUTE_VOLUME
// Modifier keys
case VK_SHIFT:
return virtualScanCode == 0x2A ? 57441 : 57447; // LEFT_SHIFT : RIGHT_SHIFT
case VK_LSHIFT:
return 57441; // LEFT_SHIFT
case VK_RSHIFT:
return 57447; // RIGHT_SHIFT
case VK_CONTROL:
return isEnhanced ? 57448 : 57442; // RIGHT_CONTROL : LEFT_CONTROL
case VK_LCONTROL:
return 57442; // LEFT_CONTROL
case VK_RCONTROL:
return 57448; // RIGHT_CONTROL
case VK_MENU:
return isEnhanced ? 57449 : 57443; // RIGHT_ALT : LEFT_ALT
case VK_LMENU:
return 57443; // LEFT_ALT
case VK_RMENU:
return 57449; // RIGHT_ALT
case VK_LWIN:
return 57444; // LEFT_SUPER
case VK_RWIN:
return 57450; // RIGHT_SUPER
default:
return 0;
}
}
std::vector<uint8_t>& TerminalInput::_getKittyStack() noexcept
{
return _inAlternateBuffer ? _kittyAltStack : _kittyMainStack;
}
bool TerminalInput::_codepointIsNonControl(uint32_t cp) noexcept
{
return cp > 0x1f && (cp < 0x7f || cp > 0x9f);
}
TerminalInput::CodepointBuffer TerminalInput::_codepointToBuffer(uint32_t cp) noexcept
{
CodepointBuffer cb;
if (cp <= 0xFFFF)
{
cb.buf[0] = static_cast<wchar_t>(cp);
cb.buf[1] = 0;
cb.len = 1;
}
else
{
cp -= 0x10000;
cb.buf[0] = static_cast<wchar_t>((cp >> 10) + 0xD800);
cb.buf[1] = static_cast<wchar_t>((cp & 0x3FF) + 0xDC00);
cb.buf[2] = 0;
cb.len = 2;
}
return cb;
}
uint32_t TerminalInput::_bufferToCodepoint(const wchar_t* str) noexcept
{
if (til::is_leading_surrogate(str[0]) && til::is_trailing_surrogate(str[1]))
{
return til::combine_surrogates(str[0], str[1]);
}
return str[0];
}
uint32_t TerminalInput::_codepointToLower(uint32_t cp) noexcept
{
auto cb = _codepointToBuffer(cp);
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.
const auto len = LCMapStringW(LOCALE_INVARIANT, LCMAP_LOWERCASE, cb.buf, cb.len, cb.buf, gsl::narrow_cast<int>(std::size(cb.buf)));
// NOTE: LCMapStringW returns the length including the null terminator. I'm not checking for it,
// because after decades, LCMapStringW should be reliable enough to return len==0 for OOM.
if (len > 1)
{
return _bufferToCodepoint(cb.buf);
}
return cp;
}

View File

@@ -47,26 +47,55 @@ namespace Microsoft::Console::VirtualTerminal
AlternateScroll
};
// Kitty keyboard protocol progressive enhancement flags
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
struct KittyKeyboardProtocolFlags
{
static constexpr uint8_t None = 0;
static constexpr uint8_t Disambiguate = 1 << 0; // Disambiguate escape codes
static constexpr uint8_t ReportEventTypes = 1 << 1; // Report event types (press/repeat/release)
static constexpr uint8_t ReportAlternateKeys = 1 << 2; // Report alternate keys
static constexpr uint8_t ReportAllKeys = 1 << 3; // Report all keys as escape codes
static constexpr uint8_t ReportText = 1 << 4; // Report associated text
static constexpr uint8_t All = (1 << 5) - 1;
};
enum class KittyKeyboardProtocolMode : uint8_t
{
Replace = 1,
Set = 2,
Reset = 3,
};
TerminalInput() noexcept;
void SetInputMode(const Mode mode, const bool enabled) noexcept;
bool GetInputMode(const Mode mode) const noexcept;
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
void SetInputMode(Mode mode, bool enabled) noexcept;
bool GetInputMode(Mode mode) const noexcept;
void ResetInputModes() noexcept;
void ForceDisableWin32InputMode(const bool win32InputMode) noexcept;
void ForceDisableWin32InputMode(bool win32InputMode) noexcept;
void ForceDisableKittyKeyboardProtocol(bool disable) noexcept;
// Kitty keyboard protocol methods
void SetKittyKeyboardProtocol(uint8_t flags, KittyKeyboardProtocolMode mode) noexcept;
uint8_t GetKittyFlags() const noexcept;
void PushKittyFlags(uint8_t flags) noexcept;
void PopKittyFlags(size_t count) noexcept;
void ResetKittyKeyboardProtocols() noexcept;
#pragma region MouseInput
// These methods are defined in mouseInput.cpp
bool IsTrackingMouseInput() const noexcept;
bool ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
#pragma endregion
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
bool ShouldSendAlternateScroll(unsigned int button, short delta) const noexcept;
#pragma endregion
private:
struct CodepointBuffer
{
wchar_t buf[3];
uint16_t len;
};
// storage location for the leading surrogate of a utf-16 surrogate pair
wchar_t _leadingSurrogate = 0;
@@ -80,24 +109,38 @@ namespace Microsoft::Console::VirtualTerminal
til::enumset<Mode> _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll };
bool _forceDisableWin32InputMode{ false };
bool _inAlternateBuffer{ false };
// Kitty keyboard protocol state - separate stacks for main and alternate screen buffers
static constexpr size_t KittyStackMaxSize = 16;
bool _forceDisableKittyKeyboardProtocol = false;
uint8_t _kittyFlags = 0;
std::vector<uint8_t> _kittyMainStack;
std::vector<uint8_t> _kittyAltStack;
const wchar_t* _csi = L"\x1B[";
const wchar_t* _ss3 = L"\x1BO";
void _initKeyboardMap() noexcept;
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
std::array<byte, 256> _getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const;
[[nodiscard]] static wchar_t _makeCtrlChar(const wchar_t ch);
std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState) const;
[[nodiscard]] static wchar_t _makeCtrlChar(wchar_t ch);
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
[[nodiscard]] static StringType _makeNoOutput() noexcept;
[[nodiscard]] void _escapeOutput(StringType& charSequence, const bool altIsPressed) const;
void _escapeOutput(StringType& charSequence, bool altIsPressed) const;
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
[[nodiscard]] OutputType _makeKittyOutput(const KEY_EVENT_RECORD& key, DWORD controlKeyState);
[[nodiscard]] static uint32_t _getKittyFunctionalKeyCode(WORD virtualKeyCode, WORD virtualScanCode, DWORD controlKeyState) noexcept;
std::vector<uint8_t>& _getKittyStack() noexcept;
static bool _codepointIsNonControl(uint32_t cp) noexcept;
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;
static uint32_t _bufferToCodepoint(const wchar_t* str) noexcept;
static uint32_t _codepointToLower(uint32_t cp) noexcept;
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
struct MouseInputState
{
bool inAlternateBuffer{ false };
til::point lastPos{ -1, -1 };
unsigned int lastButton{ 0 };
int accumulatedDelta{ 0 };
@@ -113,7 +156,7 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
static constexpr unsigned int s_GetPressedButton(MouseButtonState state) noexcept;
#pragma endregion
};
}

View File

@@ -546,6 +546,18 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
case CsiActionCodes::ANSISYSRC_CursorRestore:
_dispatch->CursorRestoreState();
break;
case CsiActionCodes::KKP_KittyKeyboardSet:
_dispatch->SetKittyKeyboardProtocol(parameters.at(0), parameters.at(1));
break;
case CsiActionCodes::KKP_KittyKeyboardQuery:
_dispatch->QueryKittyKeyboardProtocol();
break;
case CsiActionCodes::KKP_KittyKeyboardPush:
_dispatch->PushKittyKeyboardProtocol(parameters.at(0));
break;
case CsiActionCodes::KKP_KittyKeyboardPop:
_dispatch->PopKittyKeyboardProtocol(parameters.at(0));
break;
case CsiActionCodes::IL_InsertLine:
_dispatch->InsertLine(parameters.at(0));
break;

View File

@@ -136,6 +136,10 @@ namespace Microsoft::Console::VirtualTerminal
DECSLRM_SetLeftRightMargins = VTID("s"),
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
ANSISYSRC_CursorRestore = VTID("u"),
KKP_KittyKeyboardSet = VTID("=u"),
KKP_KittyKeyboardQuery = VTID("?u"),
KKP_KittyKeyboardPush = VTID(">u"),
KKP_KittyKeyboardPop = VTID("<u"),
DECREQTPARM_RequestTerminalParameters = VTID("x"),
PPA_PagePositionAbsolute = VTID(" P"),
PPR_PagePositionRelative = VTID(" Q"),

View File

@@ -0,0 +1,558 @@
<#
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
.SYNOPSIS
Scans XAML files for local:SettingContainer entries and generates GeneratedSettingsIndex.g.h / .g.cpp.
.PARAMETER SourceDir
Directory to scan recursively for .xaml files.
.PARAMETER OutputDir
Directory to place generated C++ files.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)][string]$SourceDir,
[Parameter(Mandatory=$false)][string]$OutputDir
)
# Prohibited UIDs (exact match, case-insensitive by default)
$ProhibitedUids = @(
'Extensions_Scope',
'Profile_MissingFontFaces',
'Profile_ProportionalFontFaces',
'ColorScheme_InboxSchemeDuplicate',
'ColorScheme_ColorsHeader',
'ColorScheme_Rename'
)
# Prohibited XAML files
$ProhibitedXamlFiles = @(
'CommonResources.xaml',
'KeyChordListener.xaml',
'NullableColorPicker.xaml',
'SettingContainerStyle.xaml',
'AISettings.xaml',
'Profiles_Base_Orphaned.xaml'
)
if (-not (Test-Path $SourceDir)) { throw "SourceDir not found: $SourceDir" }
if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null }
$resourceKeys = ([xml](Get-Content "$($SourceDir)\Resources\en-US\Resources.resw")).root.data.name
$entries = @()
Get-ChildItem -Path $SourceDir -Recurse -Filter *.xaml | ForEach-Object {
# Skip whole file if prohibited
$filename = $_.Name
if ($ProhibitedXamlFiles -contains $filename)
{
return
}
$text = Get-Content -Raw -LiteralPath $_.FullName
# Extract Page x:Class
$pageClass = $null
if ($text -match '<Page\b[^>]*\bx:Class="([^"]+)"')
{
$pageClass = $matches[1]
}
elseif ($filename -eq 'Appearances.xaml')
{
# Appearances.xaml is a UserControl that is hosted in Profiles_Appearance.xaml
$pageClass = 'Microsoft::Terminal::Settings::Editor::Profiles_Appearance'
}
else
{
return
}
# Convert XAML namespace dots to C++ scope operators
$pageClass = ($pageClass -replace '\.', '::')
# Deduce BreadcrumbSubPage
# Special cases:
# - NewTabMenu: defer to UID, see NavigationParam section below
# - Extensions: defer to UID, see NavigationParam section below
$subPage = 'BreadcrumbSubPage::'
if ($pageClass -match 'Editor::Profiles_Appearance')
{
$subPage += 'Profile_Appearance'
}
elseif ($pageClass -match 'Editor::Profiles_Terminal')
{
$subPage += 'Profile_Terminal'
}
elseif ($pageClass -match 'Editor::Profiles_Advanced')
{
$subPage += 'Profile_Advanced'
}
elseif ($pageClass -match 'Editor::EditColorScheme')
{
$subPage += 'ColorSchemes_Edit'
}
else
{
$subPage += 'None'
}
# Register top-level pages
if ($filename -eq 'Launch.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Launch/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Launch/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Launch_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'Interaction.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Interaction/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Interaction/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Interaction_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'GlobalAppearance.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Appearance/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Appearance/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"GlobalAppearance_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'ColorSchemes.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_ColorSchemes/Content`""
DisplayTextLocalized = "RS_(L`"Nav_ColorSchemes/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"ColorSchemes_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
# Manually register the "add new" button
$entries += [pscustomobject]@{
DisplayTextUid = "L`"ColorScheme_AddNewButton/Text`""
DisplayTextLocalized = 'RS_(L"ColorScheme_AddNewButton/Text")'
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"ColorSchemes_Nav`"})"
SubPage = 'BreadcrumbSubPage::None'
ElementName = 'L"AddNewButton"'
File = $filename
}
}
elseif ($filename -eq 'Rendering.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Rendering/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Rendering/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Rendering_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'Compatibility.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Compatibility/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Compatibility/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Compatibility_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'Actions.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Actions/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Actions/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Actions_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'NewTabMenu.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_NewTabMenu/Content`""
DisplayTextLocalized = "RS_(L`"Nav_NewTabMenu/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"NewTabMenu_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'Extensions.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_Extensions/Content`""
DisplayTextLocalized = "RS_(L`"Nav_Extensions/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"Extensions_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'Profiles_Base.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_ProfileDefaults/Content`""
DisplayTextLocalized = "RS_(L`"Nav_ProfileDefaults/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"GlobalProfile_Nav`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
}
elseif ($filename -eq 'AddProfile.xaml')
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"Nav_AddNewProfile/Content`""
DisplayTextLocalized = "RS_(L`"Nav_AddNewProfile/Content`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"AddProfile`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L""'
File = $filename
}
$entries += [pscustomobject]@{
DisplayTextUid = "L`"AddProfile_AddNewTextBlock/Text`""
DisplayTextLocalized = "RS_(L`"AddProfile_AddNewTextBlock/Text`")"
ParentPage = $pageClass
NavigationParam = "winrt::box_value(hstring{L`"AddProfile`"})"
SubPage = "BreadcrumbSubPage::None"
ElementName = 'L"AddNewButton"'
File = $filename
}
}
# Find all local:SettingContainer start tags
$pattern = '<local:SettingContainer\b([^>/]*)(/?>)'
$matchesAll = [System.Text.RegularExpressions.Regex]::Matches($text, $pattern, 'IgnoreCase')
foreach ($m in $matchesAll)
{
$attrBlock = $m.Groups[1].Value
# Extract Uid
if ($attrBlock -match '\bx:Uid="([^"]+)"')
{
$uid = $matches[1]
# Skip entry if UID prohibited
if ($ProhibitedUids -contains $uid)
{
continue
}
}
else
{
continue
}
# Extract Name
if ($attrBlock -match '\bx:Name="([^"]+)"')
{
$name = $matches[1]
}
elseif ($attrBlock -match '\bName="([^"]+)"')
{
$name = $matches[1]
}
else
{
$name = ""
}
# Profile.Appearance settings need a special prefix for the ElementName.
# This allows us to bring the element into view at runtime.
if ($filename -eq 'Appearances.xaml')
{
$name = 'App.' + $name
}
# Deduce NavigationParam
# includeInBuildIndex: include the entry in the build-time index (no special param at runtime)
# includeInPartialIndex: include the entry in the partial index, where the NavigationParam is the view model at runtime (i.e. profile vs profile defaults)
$includeInBuildIndex = $true
$includeInPartialIndex = $false
$navigationParam = 'nullptr'
if ($pageClass -match 'Editor::Launch')
{
$navigationParam = 'Launch_Nav'
}
elseif ($pageClass -match 'Editor::Interaction')
{
$navigationParam = 'Interaction_Nav'
}
elseif ($pageClass -match 'Editor::Rendering')
{
$navigationParam = 'Rendering_Nav'
}
elseif ($pageClass -match 'Editor::Compatibility')
{
$navigationParam = 'Compatibility_Nav'
}
elseif ($pageClass -match 'Editor::Actions')
{
$navigationParam = 'Actions_Nav'
}
elseif ($pageClass -match 'Editor::NewTabMenu')
{
if ($uid -match 'NewTabMenu_CurrentFolder')
{
$navigationParam = 'nullptr'
$subPage = 'BreadcrumbSubPage::NewTabMenu_Folder'
}
else
{
$navigationParam = 'NewTabMenu_Nav'
$subPage = 'BreadcrumbSubPage::None'
$includeInPartialIndex = $true
}
}
elseif ($pageClass -match 'Editor::Extensions')
{
$navigationParam = 'Extensions_Nav'
$subPage = 'BreadcrumbSubPage::None'
}
elseif ($pageClass -match 'Editor::Profiles_Base' -or
$pageClass -match 'Editor::Profiles_Appearance' -or
$pageClass -match 'Editor::Profiles_Terminal' -or
$pageClass -match 'Editor::Profiles_Advanced')
{
$navigationParam = 'GlobalProfile_Nav'
$includeInBuildIndex = !($name -eq "Name" -or $name -eq "Commandline")
$includeInPartialIndex = $true
}
elseif ($pageClass -match 'Editor::EditColorScheme')
{
# populate with color scheme name at runtime
$navigationParam = 'nullptr'
}
elseif ($pageClass -match 'Editor::GlobalAppearance')
{
$navigationParam = 'GlobalAppearance_Nav'
}
elseif ($pageClass -match 'Editor::AddProfile')
{
$navigationParam = 'AddProfile'
}
if ($includeInBuildIndex)
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"$($uid)/Header`""
DisplayTextLocalized = "RS_(L`"$($uid)/Header`")"
ParentPage = $pageClass
NavigationParam = $navigationParam -eq "nullptr" ? $navigationParam : "winrt::box_value(hstring{L`"$($navigationParam)`"})"
SubPage = $subPage
ElementName = "L`"$($name)`""
File = $filename
}
}
if ($includeInPartialIndex)
{
$entries += [pscustomobject]@{
DisplayTextUid = "L`"$($uid)/Header`""
DisplayTextLocalized = "RS_(L`"$($uid)/Header`")"
ParentPage = $pageClass
NavigationParam = 'nullptr' # VM param at runtime
SubPage = $navigationParam -eq 'NewTabMenu_Nav' ? 'BreadcrumbSubPage::NewTabMenu_Folder' : $subPage
ElementName = "L`"$($name)`""
File = $filename
}
}
}
}
# Ensure there aren't any duplicate entries
$entries = $entries | Sort-Object DisplayTextLocalized, ParentPage, NavigationParam, SubPage, ElementName, File -Unique
$buildTimeEntries = @()
$profileEntries = @()
$schemeEntries = @()
$ntmEntries = @()
foreach ($e in $entries)
{
$formattedEntry = " IndexEntry{ $($e.DisplayTextUid), $($e.DisplayTextLocalized), $($e.NavigationParam), $($e.SubPage), $($e.ElementName) }, // $($e.File)"
if ($e.NavigationParam -eq 'nullptr' -and
($e.ParentPage -match 'Profiles_Base' -or
$e.ParentPage -match 'Profiles_Appearance' -or
$e.ParentPage -match 'Profiles_Terminal' -or
$e.ParentPage -match 'Profiles_Advanced'))
{
$profileEntries += $formattedEntry
}
elseif ($e.SubPage -eq 'BreadcrumbSubPage::ColorSchemes_Edit')
{
$schemeEntries += $formattedEntry
}
elseif ($e.SubPage -eq 'BreadcrumbSubPage::NewTabMenu_Folder')
{
$ntmEntries += $formattedEntry
}
else
{
$buildTimeEntries += $formattedEntry
}
}
$headerPath = Join-Path $OutputDir 'GeneratedSettingsIndex.g.h'
$cppPath = Join-Path $OutputDir 'GeneratedSettingsIndex.g.cpp'
$header = @"
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
--*/
#pragma once
#include <winrt/Windows.UI.Xaml.Interop.h>
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct IndexEntry
{
// x:Uid of the SettingContainer's Header (i.e. "Globals_DefaultProfile/Header")
hstring DisplayTextUid;
// Localized display text shown in the SettingContainer (i.e. RS_(L"Globals_DefaultProfile/Header"))
hstring DisplayTextLocalized;
// Navigation argument (i.e. winrt::box_value(hstring) or nullptr)
// Use nullptr as placeholder for runtime navigation with a view model object
winrt::Windows::Foundation::IInspectable NavigationArg;
BreadcrumbSubPage SubPage;
// x:Name of the SettingContainer to navigate to on the page (i.e. "DefaultProfile")
hstring ElementName;
};
const std::array<IndexEntry, $($buildTimeEntries.Count)>& LoadBuildTimeIndex();
const std::array<IndexEntry, $($profileEntries.Count)>& LoadProfileIndex();
const std::array<IndexEntry, $($ntmEntries.Count)>& LoadNTMFolderIndex();
const std::array<IndexEntry, $($schemeEntries.Count)>& LoadColorSchemeIndex();
const IndexEntry& PartialProfileIndexEntry();
const IndexEntry& PartialNTMFolderIndexEntry();
const IndexEntry& PartialColorSchemeIndexEntry();
const IndexEntry& PartialExtensionIndexEntry();
const IndexEntry& PartialActionIndexEntry();
}
"@
$cpp = @"
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include "GeneratedSettingsIndex.g.h"
#include <LibraryResources.h>
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
const std::array<IndexEntry, $($buildTimeEntries.Count)>& LoadBuildTimeIndex()
{
static std::array<IndexEntry, $($buildTimeEntries.Count)> entries =
{
$( ($buildTimeEntries -join "`r`n") )
};
return entries;
}
const std::array<IndexEntry, $($profileEntries.Count)>& LoadProfileIndex()
{
static std::array<IndexEntry, $($profileEntries.Count)> entries =
{
$( ($profileEntries -join "`r`n") )
};
return entries;
}
const std::array<IndexEntry, $($ntmEntries.Count)>& LoadNTMFolderIndex()
{
static std::array<IndexEntry, $($ntmEntries.Count)> entries =
{
$( ($ntmEntries -join "`r`n") )
};
return entries;
}
const std::array<IndexEntry, $($schemeEntries.Count)>& LoadColorSchemeIndex()
{
static std::array<IndexEntry, $($schemeEntries.Count)> entries =
{
$( ($schemeEntries -join "`r`n") )
};
return entries;
}
const IndexEntry& PartialProfileIndexEntry()
{
static IndexEntry entry{ L"", L"", nullptr, BreadcrumbSubPage::None, L"" };
return entry;
}
const IndexEntry& PartialNTMFolderIndexEntry()
{
static IndexEntry entry{ L"", L"", nullptr, BreadcrumbSubPage::NewTabMenu_Folder, L"" };
return entry;
}
const IndexEntry& PartialColorSchemeIndexEntry()
{
static IndexEntry entry{ L"", L"", nullptr, BreadcrumbSubPage::ColorSchemes_Edit, L"" };
return entry;
}
const IndexEntry& PartialExtensionIndexEntry()
{
static IndexEntry entry{ L"", L"", nullptr, BreadcrumbSubPage::Extensions_Extension, L"" };
return entry;
}
const IndexEntry& PartialActionIndexEntry()
{
static IndexEntry entry{ L"", L"", nullptr, BreadcrumbSubPage::Actions_Edit, L"" };
return entry;
}
}
"@
Set-Content -LiteralPath $headerPath -Value $header -NoNewline
Set-Content -LiteralPath $cppPath -Value $cpp -NoNewline
Write-Host "Generated:"
Write-Host " $headerPath"
Write-Host " $cppPath"