terminal/src/cascadia/TerminalSettingsEditor/ColorSchemes.cpp
Carlos Zamora 33470ad08e
Add UI for adding, renaming, and deleting a color scheme (#8403)
Introduces the following UI controls to the ColorSchemes page:
- "Add new" button
  - next to dropdown selector
  - adds a new color scheme named ("Color Scheme #" where # is the number of color schemes you have)
- "Rename" Button
  - next to the selector
  - replaces the ComboBox with a TextBox and the accept/cancel buttons appear
- "Delete" button
  - bottom of the page
  - opens flyout, when confirmed, deletes the current color scheme and selects another one

This also adds a Delete button to the Profiles page. The Hide checkbox was moved above the Delete button.

## References
#1564 - Settings UI
#6800 - Settings UI Completion Epic

## Detailed Description of the Pull Request / Additional comments

**Color Schemes:**
- Deleting a color scheme selects another one from the list available
- Rename replaces the combobox with a textbox to allow editing
- The Add New button creates a new color scheme named "Color Scheme X" where X is the number of schemes defined
- In-box color schemes cannot be deleted

**Profile:**
- Deleting a profile selects another one from the list available
- the rename button does not exist (yet), because it needs a modification to the NavigationView's Header Template
- The delete button is disabled for in-box profiles (CMD and Windows Powershell) and dynamic profiles

## Validation Steps Performed
**Color Schemes - Add New**
 Creates a new color scheme named "Color Scheme X" (X being the number of color schemes)
 The new color scheme can be renamed/deleted/modified

**Color Schemes - Rename**
 You cannot rename an in-box color scheme
 The rename button has a tooltip
 Clicking the rename button replaces the combobox with a textbox
 Accept --> changes name
 Cancel --> does not change the name
 accepting/cancelling the rename operation updates the combo box appropriately

**Color Schemes - Delete**
 Clicking delete produces a flyout to confirm deletion
 Deleting a color scheme removes it from the list and select the one under it
 Deleting the last color scheme selects the last available color scheme after it's deleted
 In-box color schemes have the delete button disabled, and a disclaimer appears next to it

**Profile- Delete**
 Base layer presents a disclaimer at the top, and hides the delete button
 Dynamic and in-box profiles disable the delete button and show the appropriate disclaimer next to the disabled button
 Clicking delete produces a flyout to confirm deletion
 Regular profiles have a delete button that is styled appropriately
 Clicking the delete profile button opens a content dialog. Confirmation deletes the profile and navigates to the profile indexed under it (deleting the last one redirects to the last one)


## Demo
Refer to this post [here](https://github.com/microsoft/terminal/pull/8403#issuecomment-747545651.
Confirmation flyout demo: https://github.com/microsoft/terminal/pull/8403#issuecomment-747657842
2020-12-17 23:14:07 +00:00

265 lines
9.8 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ColorSchemes.h"
#include "ColorTableEntry.g.cpp"
#include "ColorSchemes.g.cpp"
#include "ColorSchemesPageNavigationState.g.cpp"
#include <LibraryResources.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;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static const std::array<hstring, 16> TableColorNames = {
RS_(L"ColorScheme_Black/Header"),
RS_(L"ColorScheme_Red/Header"),
RS_(L"ColorScheme_Green/Header"),
RS_(L"ColorScheme_Yellow/Header"),
RS_(L"ColorScheme_Blue/Header"),
RS_(L"ColorScheme_Purple/Header"),
RS_(L"ColorScheme_Cyan/Header"),
RS_(L"ColorScheme_White/Header"),
RS_(L"ColorScheme_BrightBlack/Header"),
RS_(L"ColorScheme_BrightRed/Header"),
RS_(L"ColorScheme_BrightGreen/Header"),
RS_(L"ColorScheme_BrightYellow/Header"),
RS_(L"ColorScheme_BrightBlue/Header"),
RS_(L"ColorScheme_BrightPurple/Header"),
RS_(L"ColorScheme_BrightCyan/Header"),
RS_(L"ColorScheme_BrightWhite/Header")
};
static const std::array<std::wstring, 9> InBoxSchemes = {
L"Campbell",
L"Campbell Powershell",
L"Vintage",
L"One Half Dark",
L"One Half Light",
L"Solarized Dark",
L"Solarized Light",
L"Tango Dark",
L"Tango Light"
};
ColorSchemes::ColorSchemes() :
_ColorSchemeList{ single_threaded_observable_vector<Model::ColorScheme>() },
_CurrentColorTable{ single_threaded_observable_vector<Editor::ColorTableEntry>() }
{
InitializeComponent();
}
void ColorSchemes::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::ColorSchemesPageNavigationState>();
_UpdateColorSchemeList();
// Initialize our color table view model with 16 dummy colors
// so that on a ColorScheme selection change, we can loop through
// each ColorTableEntry and just change its color. Performing a
// clear and 16 appends doesn't seem to update the color pickers
// very accurately.
for (uint8_t i = 0; i < TableColorNames.size(); ++i)
{
auto entry = winrt::make<ColorTableEntry>(i, Windows::UI::Color{ 0, 0, 0, 0 });
_CurrentColorTable.Append(entry);
}
}
// Function Description:
// - Called when a different color scheme is selected. Updates our current
// color scheme and updates our currently modifiable color table.
// Arguments:
// - args: The selection changed args that tells us what's the new color scheme selected.
// Return Value:
// - <none>
void ColorSchemes::ColorSchemeSelectionChanged(IInspectable const& /*sender*/,
SelectionChangedEventArgs const& args)
{
// Update the color scheme this page is modifying
const auto colorScheme{ args.AddedItems().GetAt(0).try_as<Model::ColorScheme>() };
CurrentColorScheme(colorScheme);
_UpdateColorTable(colorScheme);
// Set the text disclaimer for the text box
hstring disclaimer{};
const std::wstring schemeName{ colorScheme.Name() };
if (std::find(std::begin(InBoxSchemes), std::end(InBoxSchemes), schemeName) != std::end(InBoxSchemes))
{
// load disclaimer for in-box profiles
disclaimer = RS_(L"ColorScheme_DeleteButtonDisclaimerInBox");
}
DeleteButtonDisclaimer().Text(disclaimer);
// Update the state of the page
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CanDeleteCurrentScheme" });
IsRenaming(false);
}
// Function Description:
// - Updates the list of all color schemes available to choose from.
// Arguments:
// - <none>
// Return Value:
// - <none>
void ColorSchemes::_UpdateColorSchemeList()
{
// Surprisingly, though this is called every time we navigate to the page,
// the list does not keep growing on each navigation.
const auto& colorSchemeMap{ _State.Globals().ColorSchemes() };
for (const auto& pair : colorSchemeMap)
{
_ColorSchemeList.Append(pair.Value());
}
}
// Function Description:
// - Called when a ColorPicker control has selected a new color. This is specifically
// called by color pickers assigned to a color table entry. It takes the index
// that's been stuffed in the Tag property of the color picker and uses it
// to update the color table accordingly.
// Arguments:
// - sender: the color picker that raised this event.
// - args: the args that contains the new color that was picked.
// Return Value:
// - <none>
void ColorSchemes::ColorPickerChanged(IInspectable const& sender,
ColorChangedEventArgs const& args)
{
if (auto picker = sender.try_as<ColorPicker>())
{
if (auto tag = picker.Tag())
{
auto index = winrt::unbox_value<uint8_t>(tag);
CurrentColorScheme().SetColorTableEntry(index, args.NewColor());
_CurrentColorTable.GetAt(index).Color(args.NewColor());
}
}
}
bool ColorSchemes::CanDeleteCurrentScheme() const
{
if (const auto scheme{ CurrentColorScheme() })
{
// Only allow this color scheme to be deleted if it's not provided in-box
const std::wstring myName{ scheme.Name() };
return std::find(std::begin(InBoxSchemes), std::end(InBoxSchemes), myName) == std::end(InBoxSchemes);
}
return false;
}
void ColorSchemes::DeleteConfirmation_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
const auto schemeName{ CurrentColorScheme().Name() };
_State.Globals().RemoveColorScheme(schemeName);
const auto removedSchemeIndex{ ColorSchemeComboBox().SelectedIndex() };
if (static_cast<uint32_t>(removedSchemeIndex) < _ColorSchemeList.Size() - 1)
{
// select same index
ColorSchemeComboBox().SelectedIndex(removedSchemeIndex + 1);
}
else
{
// select last color scheme (avoid out of bounds error)
ColorSchemeComboBox().SelectedIndex(removedSchemeIndex - 1);
}
_ColorSchemeList.RemoveAt(removedSchemeIndex);
DeleteButton().Flyout().Hide();
}
void ColorSchemes::AddNew_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
// Give the new scheme a distinct name
const hstring schemeName{ fmt::format(L"Color Scheme {}", _State.Globals().ColorSchemes().Size() + 1) };
Model::ColorScheme scheme{ schemeName };
// Add the new color scheme
_State.Globals().AddColorScheme(scheme);
// Update current page
_ColorSchemeList.Append(scheme);
ColorSchemeComboBox().SelectedItem(scheme);
}
// Function Description:
// - Pre-populates/focuses the name TextBox, updates the UI
// Arguments:
// - <unused>
// Return Value:
// - <none>
void ColorSchemes::Rename_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
NameBox().Text(CurrentColorScheme().Name());
IsRenaming(true);
NameBox().Focus(FocusState::Programmatic);
NameBox().SelectAll();
}
void ColorSchemes::RenameAccept_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
_RenameCurrentScheme(NameBox().Text());
}
void ColorSchemes::RenameCancel_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
IsRenaming(false);
}
void ColorSchemes::NameBox_PreviewKeyDown(IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Enter)
{
_RenameCurrentScheme(NameBox().Text());
e.Handled(true);
}
else if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Escape)
{
IsRenaming(false);
e.Handled(true);
}
}
void ColorSchemes::_RenameCurrentScheme(hstring newName)
{
CurrentColorScheme().Name(newName);
IsRenaming(false);
// The color scheme is renamed appropriately, but the ComboBox still shows the old name (until you open it)
// We need to manually force the ComboBox to refresh itself.
const auto selectedIndex{ ColorSchemeComboBox().SelectedIndex() };
ColorSchemeComboBox().SelectedIndex((selectedIndex + 1) % ColorSchemeList().Size());
ColorSchemeComboBox().SelectedIndex(selectedIndex);
}
// Function Description:
// - Updates the currently modifiable color table based on the given current color scheme.
// Arguments:
// - colorScheme: the color scheme to retrieve the color table from
// Return Value:
// - <none>
void ColorSchemes::_UpdateColorTable(const Model::ColorScheme& colorScheme)
{
for (uint8_t i = 0; i < TableColorNames.size(); ++i)
{
_CurrentColorTable.GetAt(i).Color(colorScheme.Table()[i]);
}
}
ColorTableEntry::ColorTableEntry(uint8_t index, Windows::UI::Color color)
{
Name(TableColorNames[index]);
Index(winrt::box_value<uint8_t>(index));
Color(color);
}
}