minimum viable product

This commit is contained in:
Carlos Zamora 2025-10-28 12:59:31 -07:00
parent 915f085b60
commit cac844b1e9
5 changed files with 233 additions and 25 deletions

View File

@ -26,6 +26,7 @@
#include <LibraryResources.h>
#include <dwmapi.h>
#include <fmt/compile.h>
namespace winrt
{
@ -463,31 +464,31 @@ 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<NavigateToLaunchArgs>(winrt::make<LaunchViewModel>(_settingsClone)));
contentFrame().Navigate(xaml_typename<Editor::Launch>(), winrt::make<NavigateToLaunchArgs>(winrt::make<LaunchViewModel>(_settingsClone), elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Launch/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
else if (clickedItemTag == interactionTag)
{
contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToInteractionArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings())));
contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToInteractionArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings()), elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Interaction/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
else if (clickedItemTag == renderingTag)
{
contentFrame().Navigate(xaml_typename<Editor::Rendering>(), winrt::make<NavigateToRenderingArgs>(winrt::make<RenderingViewModel>(_settingsClone)));
contentFrame().Navigate(xaml_typename<Editor::Rendering>(), winrt::make<NavigateToRenderingArgs>(winrt::make<RenderingViewModel>(_settingsClone), elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Rendering/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
else if (clickedItemTag == compatibilityTag)
{
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<NavigateToCompatibilityArgs>(winrt::make<CompatibilityViewModel>(_settingsClone)));
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<NavigateToCompatibilityArgs>(winrt::make<CompatibilityViewModel>(_settingsClone), elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Compatibility/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
@ -508,7 +509,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
else
{
// Navigate to the NewTabMenu page
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToNewTabMenuArgs>(_newTabMenuPageVM));
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToNewTabMenuArgs>(_newTabMenuPageVM, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
@ -523,7 +524,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else
{
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToExtensionsArgs>(_extensionsVM));
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToExtensionsArgs>(_extensionsVM, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
@ -536,7 +537,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_SetupProfileEventHandling(profileVM);
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profileVM, *this));
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profileVM, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_ProfileDefaults/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
@ -558,7 +559,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
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>(), winrt::make<NavigateToColorSchemesArgs>(_colorSchemesPageVM));
contentFrame().Navigate(xaml_typename<Editor::ColorSchemes>(), winrt::make<NavigateToColorSchemesArgs>(_colorSchemesPageVM, elementToFocus));
if (subPage == BreadcrumbSubPage::ColorSchemes_Edit)
{
@ -567,13 +568,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (clickedItemTag == globalAppearanceTag)
{
contentFrame().Navigate(xaml_typename<Editor::GlobalAppearance>(), winrt::make<NavigateToGlobalAppearanceArgs>(winrt::make<GlobalAppearanceViewModel>(_settingsClone.GlobalSettings())));
contentFrame().Navigate(xaml_typename<Editor::GlobalAppearance>(), winrt::make<NavigateToGlobalAppearanceArgs>(winrt::make<GlobalAppearanceViewModel>(_settingsClone.GlobalSettings()), elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Appearance/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
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);
@ -586,7 +587,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();
@ -594,14 +595,14 @@ 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<implementation::NavigateToProfileArgs>(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<implementation::NavigateToProfileArgs>(profile, *this, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(profile), profile.Name(), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
@ -625,11 +626,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage)
void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage, hstring elementToFocus)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToNewTabMenuArgs>(_newTabMenuPageVM));
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToNewTabMenuArgs>(_newTabMenuPageVM, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
@ -658,11 +659,11 @@ 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>(), winrt::make<NavigateToExtensionsArgs>(_extensionsVM));
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), winrt::make<NavigateToExtensionsArgs>(_extensionsVM, elementToFocus));
const auto crumb = winrt::make<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
@ -906,7 +907,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
uint32_t vmIndex;
if (_menuItemSource.IndexOf(profileVM, vmIndex))
{
_profileVMs.RemoveAt(vmIndex);
_profileVMs.RemoveAt(vmIndex);
}
// navigate to the profile next to this one
@ -1022,13 +1023,145 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
safe_void_coroutine MainPage::SettingsSearchBox_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& args)
{
// TODO CARLOS: debug this function. May still need debounce?
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;
}
// search the index on the background thread
co_await winrt::resume_background();
const auto searchGeneration = _QuerySearchIndex(sanitizedQuery);
// convert and display results on the foreground thread
co_await winrt::resume_foreground(Dispatcher());
if (searchGeneration != _filteredSearchIndex.generation())
{
// search index was updated while we were searching, discard results
co_return;
}
// must be IInspectable to be used as ItemsSource
std::vector<IInspectable> results;
if (_filteredSearchIndex->empty())
{
// Explicitly show "no results"
// TODO CARLOS: use RS_switchable_fmt from ActionArgs.cpp
const hstring noResultsText{ fmt::format(fmt::runtime(std::wstring{ RS_(L"Search_NoResults") }), sanitizedQuery) };
results.reserve(1);
results.push_back(winrt::make<FilteredSearchResult>(noResultsText));
}
else
{
for (const auto* indexEntry : *_filteredSearchIndex)
{
results.push_back(winrt::make<FilteredSearchResult>(*indexEntry));
}
}
// Update the UI with the results
const auto& searchBox = SettingsSearchBox();
searchBox.ItemsSource(winrt::single_threaded_observable_vector<IInspectable>(std::move(results)));
searchBox.IsSuggestionListOpen(true);
}
// Update _filteredSearchIndex with results matching queryText
til::generation_t MainPage::_QuerySearchIndex(const hstring& queryText)
{
auto filteredResults{ _filteredSearchIndex.write() };
filteredResults->clear();
for (const auto& entry : _searchIndex)
{
// TODO CARLOS: replace with fuzzy search
const auto& displayText = entry.DisplayText;
if (til::contains_linguistic_insensitive(displayText, queryText))
{
filteredResults->push_back(&entry);
}
}
return _filteredSearchIndex.generation();
}
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& navigationParam{ indexEntry.NavigationParam };
if (navigationParam.try_as<hstring>())
{
_Navigate(navigationParam.as<hstring>(), indexEntry.SubPage, indexEntry.ElementName);
}
else if (const auto profileVM = navigationParam.try_as<ProfileViewModel>())
{
_Navigate(*profileVM, indexEntry.SubPage, indexEntry.ElementName);
}
else if (const auto ntmEntryVM = navigationParam.try_as<NewTabMenuEntryViewModel>())
{
_Navigate(*ntmEntryVM, indexEntry.SubPage, indexEntry.ElementName);
}
else if (const auto extPkgVM = navigationParam.try_as<ExtensionPackageViewModel>())
{
_Navigate(*extPkgVM, indexEntry.SubPage, indexEntry.ElementName);
}
}
}
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()
}
void MainPage::_UpdateSearchIndex()
{
_searchIndex = {};
// TODO CARLOS: delay evaluating resources to here.
// - we also want to allow searching in English regardless or the selected language
// - we still want to only show the native language though
// - we should update the index to use USES_RESOURCE instead of RS_, then evaluate resources here
const auto buildIndex = LoadBuildTimeIndex();
_searchIndex.reserve(buildIndex.size());
_searchIndex.assign(buildIndex.begin(), buildIndex.end());
_searchIndex.insert(_searchIndex.end(), buildIndex.begin(), buildIndex.end());
// Load profiles
for (const auto& profile : _profileVMs)

View File

@ -5,8 +5,10 @@
#include "MainPage.g.h"
#include "Breadcrumb.g.h"
#include "FilteredSearchResult.g.h"
#include "Utils.h"
#include "GeneratedSettingsIndex.g.h"
#include <til/generational.h>
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@ -24,6 +26,44 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(BreadcrumbSubPage, SubPage);
};
struct FilteredSearchResult : FilteredSearchResultT<FilteredSearchResult>
{
FilteredSearchResult(const winrt::hstring& label) :
_overrideLabel{ label } {}
FilteredSearchResult(const IndexEntry& entry) :
_SearchIndexEntry{ &entry } {}
hstring ToString() { return Label(); }
winrt::hstring Label() const
{
if (_overrideLabel)
{
return _overrideLabel.value();
}
else if (_SearchIndexEntry)
{
return _SearchIndexEntry->DisplayText;
}
return {};
}
bool IsNoResultsPlaceholder() const
{
return _overrideLabel.has_value();
}
const IndexEntry& SearchIndexEntry() const noexcept
{
return *_SearchIndexEntry;
}
private:
std::optional<winrt::hstring> _overrideLabel{ std::nullopt };
const IndexEntry* _SearchIndexEntry{ nullptr };
};
struct MainPage : MainPageT<MainPage>
{
MainPage() = delete;
@ -31,6 +71,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,15 +112,17 @@ 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::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage, hstring elementToFocus = {});
void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid);
void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args);
void _UpdateBackgroundForMica();
void _MoveXamlParsedNavItemsIntoItemSource();
til::generation_t _QuerySearchIndex(const hstring& queryText);
void _UpdateSearchIndex();
Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel> _profileVMs{ nullptr };
@ -85,6 +131,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr };
std::vector<IndexEntry> _searchIndex;
til::generational<std::vector<const IndexEntry*>> _filteredSearchIndex;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;

View File

@ -26,6 +26,11 @@ namespace Microsoft.Terminal.Settings.Editor
Extensions_Extension
};
runtimeclass FilteredSearchResult : Windows.Foundation.IStringable
{
String Label { get; };
}
runtimeclass Breadcrumb : Windows.Foundation.IStringable
{
IInspectable Tag;

View File

@ -99,6 +99,21 @@
</Grid>
</muxc:NavigationView.Header>
<muxc:NavigationView.AutoSuggestBox>
<AutoSuggestBox x:Uid="Nav_SearchBox"
x:Name="SettingsSearchBox"
QueryIcon="Find"
TextChanged="SettingsSearchBox_TextChanged"
QuerySubmitted="SettingsSearchBox_QuerySubmitted"
SuggestionChosen="SettingsSearchBox_SuggestionChosen">
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="local:FilteredSearchResult">
<TextBlock Text="{x:Bind Label}" />
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
</muxc:NavigationView.AutoSuggestBox>
<muxc:NavigationView.MenuItems>
<muxc:NavigationViewItem x:Uid="Nav_Launch"

View File

@ -2467,4 +2467,12 @@
<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>
</root>