Skip to content

Commit

Permalink
Improve Profile.Icon UI in settings (#17965)
Browse files Browse the repository at this point in the history
## Summary of the Pull Request
Improves the UI for the Profile.Icon setting by adding an "Icon Type"
combo box. This allows the user to pick from multiple options:
- None: sets the icon to "none" which is interpreted as no icon
- Built-in Icon: presents a combo box that enumerates the Segoe MDL 2
assets
- Emoji: presents a text box with a hint to open the emoji picker
- File: presents a text box to input the path of the image to use

Additionally, the rendered icon is displayed in the setting container.
If "none", "none" is presented to the user (localized).
✅ Verified as accessible using Accessibility Insights
#10000
  • Loading branch information
carlos-zamora authored Oct 22, 2024
1 parent 15a460f commit 460b180
Show file tree
Hide file tree
Showing 12 changed files with 1,760 additions and 40 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/excludes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
^doc/reference/windows-terminal-logo\.ans$
^oss/
^samples/PixelShaders/Screenshots/
^src/cascadia/TerminalSettingsEditor/SegoeFluentIconList.h$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/atlas/
^src/renderer/wddmcon/WddmConRenderer\.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<DependentUpon>ActionsViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="SegoeFluentIconList.h" />
<ClInclude Include="TerminalColorConverters.h">
<DependentUpon>TerminalColorConverters.idl</DependentUpon>
<SubType>Code</SubType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ClInclude Include="pch.h" />
<ClInclude Include="Utils.h" />
<ClInclude Include="PreviewConnection.h" />
<ClInclude Include="SegoeFluentIconList.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="ProfileViewModel.idl" />
Expand Down
182 changes: 168 additions & 14 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <LibraryResources.h>
#include "../WinRTUtils/inc/Utils.h"
#include "../../renderer/base/FontCache.h"
#include "SegoeFluentIconList.h"

using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Xaml;
Expand All @@ -26,6 +27,7 @@ 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::IVector<IInspectable> ProfileViewModel::_BuiltInIcons{ nullptr };

static constexpr std::wstring_view HideIconValue{ L"none" };

Expand All @@ -40,6 +42,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content");

// set up IconTypes
_IconTypes = winrt::single_threaded_vector<IInspectable>();
_IconTypes.Append(make<EnumEntry>(RS_(L"Profile_IconTypeNone"), box_value(IconType::None)));
_IconTypes.Append(make<EnumEntry>(RS_(L"Profile_IconTypeFontIcon"), box_value(IconType::FontIcon)));
_IconTypes.Append(make<EnumEntry>(RS_(L"Profile_IconTypeEmoji"), box_value(IconType::Emoji)));
_IconTypes.Append(make<EnumEntry>(RS_(L"Profile_IconTypeImage"), box_value(IconType::Image)));
_DeduceCurrentIconType();

// 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.
Expand Down Expand Up @@ -74,7 +84,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (viewModelProperty == L"Icon")
{
_NotifyChanges(L"HideIcon");
_DeduceCurrentIconType();
_NotifyChanges(L"LocalizedIcon", L"EvaluatedIcon", L"IconPreview");
}
else if (viewModelProperty == L"CurrentIconType")
{
_NotifyChanges(L"UsingNoIcon", L"UsingBuiltInIcon", L"UsingEmojiIcon", L"UsingImageIcon");
}
else if (viewModelProperty == L"CurrentBuiltInIcon")
{
Icon(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
}
else if (viewModelProperty == L"CurrentEmojiIcon")
{
Icon(CurrentEmojiIcon());
}
});

Expand All @@ -90,6 +113,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
UpdateFontList();
}

if (!_BuiltInIcons)
{
_UpdateBuiltInIcons();
}
_DeduceCurrentBuiltInIcon();

if (profile.HasUnfocusedAppearance())
{
_unfocusedAppearanceViewModel = winrt::make<implementation::AppearanceViewModel>(profile.UnfocusedAppearance().try_as<AppearanceConfig>());
Expand All @@ -98,6 +127,58 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_defaultAppearanceViewModel.IsDefault(true);
}

void ProfileViewModel::_UpdateBuiltInIcons()
{
_BuiltInIcons = single_threaded_vector<IInspectable>();
for (auto& [val, name] : s_SegoeFluentIcons)
{
_BuiltInIcons.Append(make<EnumEntry>(hstring{ name }, box_value(val)));
}
}

void ProfileViewModel::_DeduceCurrentIconType()
{
const auto& profileIcon = _profile.Icon();
if (profileIcon == HideIconValue)
{
CurrentIconType(_IconTypes.GetAt(0));
}
else if (L"\uE700" <= profileIcon && profileIcon <= L"\uF8B3")
{
CurrentIconType(_IconTypes.GetAt(1));
_DeduceCurrentBuiltInIcon();
}
else if (profileIcon.size() <= 2)
{
// 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));
}
}

void ProfileViewModel::_DeduceCurrentBuiltInIcon()
{
if (!_BuiltInIcons)
{
_UpdateBuiltInIcons();
}
const auto& profileIcon = Icon();
for (uint32_t i = 0; i < _BuiltInIcons.Size(); i++)
{
const auto& builtIn = _BuiltInIcons.GetAt(i);
if (profileIcon == unbox_value<hstring>(builtIn.as<Editor::EnumEntry>().EnumValue()))
{
_CurrentBuiltInIcon = builtIn;
return;
}
}
_CurrentBuiltInIcon = _BuiltInIcons.GetAt(0);
}

Model::TerminalSettings ProfileViewModel::TermSettings() const
{
return Model::TerminalSettings::CreateForPreview(_appSettings, _profile);
Expand Down Expand Up @@ -328,24 +409,97 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}

bool ProfileViewModel::HideIcon()
{
return Icon() == HideIconValue;
}
void ProfileViewModel::HideIcon(const bool hide)
winrt::hstring ProfileViewModel::LocalizedIcon() const
{
if (hide)
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::None)
{
// Stash the current value of Icon. If the user
// checks and un-checks the "Hide Icon" checkbox, we want
// the path that we display in the text box to remain unchanged.
_lastIcon = Icon();
Icon(HideIconValue);
return RS_(L"Profile_IconTypeNone");
}
else
return Icon();
}

Windows::UI::Xaml::Controls::IconElement ProfileViewModel::IconPreview() const
{
// IconWUX sets the icon width/height to 32 by default
auto icon = Microsoft::Terminal::UI::IconPathConverter::IconWUX(EvaluatedIcon());
icon.Width(16);
icon.Height(16);
return icon;
}

void ProfileViewModel::CurrentIconType(const Windows::Foundation::IInspectable& value)
{
if (_currentIconType != value)
{
Icon(_lastIcon);
// 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 = Icon();
}

// 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:
{
Icon(HideIconValue);
break;
}
case IconType::Image:
{
if (!_lastIconPath.empty())
{
// Conversely, if we switch to Image,
// retrieve that saved value and apply it
Icon(_lastIconPath);
}
break;
}
case IconType::FontIcon:
{
if (_CurrentBuiltInIcon)
{
Icon(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
}
break;
}
case IconType::Emoji:
{
CurrentEmojiIcon({});
}
}
_NotifyChanges(L"CurrentIconType");
}
};

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);
}

bool ProfileViewModel::IsBellStyleFlagSet(const uint32_t flag)
Expand Down
27 changes: 22 additions & 5 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ 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::IVector<IInspectable> BuiltInIcons() noexcept { return _BuiltInIcons; };

ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings);
Model::TerminalSettings TermSettings() const;
Expand Down Expand Up @@ -60,16 +61,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
return _profile.EvaluatedIcon();
}
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;

// starting directory
bool UseParentProcessDirectory();
void UseParentProcessDirectory(const bool useParent);
bool UseCustomStartingDirectory();

// icon
bool HideIcon();
void HideIcon(const bool hide);

// general profile knowledge
winrt::guid OriginalProfileGuid() const noexcept;
bool CanDeleteProfile() const;
Expand All @@ -88,6 +96,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
til::typed_event<Editor::ProfileViewModel, Editor::DeleteProfileEventArgs> DeleteProfileRequested;

VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::IInspectable, CurrentBuiltInIcon);
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon);

PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType);
Expand Down Expand Up @@ -119,6 +129,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);
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);
Expand All @@ -128,14 +139,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::guid _originalProfileGuid{};
winrt::hstring _lastBgImagePath;
winrt::hstring _lastStartingDirectoryPath;
winrt::hstring _lastIcon;
winrt::hstring _lastIconPath;
Windows::Foundation::IInspectable _currentIconType{};
Editor::AppearanceViewModel _defaultAppearanceViewModel;

static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
static Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> _BuiltInIcons;

Model::CascadiaSettings _appSettings;
Editor::AppearanceViewModel _unfocusedAppearanceViewModel;

void _UpdateBuiltInIcons();
void _DeduceCurrentIconType();
void _DeduceCurrentBuiltInIcon();
};

struct DeleteProfileEventArgs :
Expand Down
21 changes: 20 additions & 1 deletion src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ namespace Microsoft.Terminal.Settings.Editor
Advanced = 2
};

enum IconType
{
None = 0,
FontIcon,
Image,
Emoji
};

runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; };
Expand Down Expand Up @@ -63,7 +71,6 @@ namespace Microsoft.Terminal.Settings.Editor
ProfileSubPage CurrentPage;
Boolean UseParentProcessDirectory;
Boolean UseCustomStartingDirectory { get; };
Boolean HideIcon;
AppearanceViewModel DefaultAppearance { get; };
Guid OriginalProfileGuid { get; };
Boolean HasUnfocusedAppearance { get; };
Expand All @@ -75,7 +82,19 @@ namespace Microsoft.Terminal.Settings.Editor
Boolean AutoMarkPromptsAvailable { get; };
Boolean RepositionCursorWithMouseAvailable { get; };

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; };

IInspectable CurrentBuiltInIcon;
Windows.Foundation.Collections.IVector<IInspectable> BuiltInIcons { get; };

void CreateUnfocusedAppearance();
void DeleteUnfocusedAppearance();
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_Profile.StartingDirectory(folder);
}
}

Windows::UI::Xaml::Controls::IconSource Profiles_Base::BuiltInIconConverter(const IInspectable& iconVal)
{
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
}
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
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);

static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);

til::property_changed_event PropertyChanged;
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);

Expand Down
Loading

0 comments on commit 460b180

Please sign in to comment.