Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Profile.Icon UI in settings #17965

Open
wants to merge 6 commits into
base: dev/cazamor/SUI/nullable-color-picker
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw right now we do this once per profile when we could do it once per settings UI

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, _BuiltInIcons is static so it's a shared instance across all ProfileVMs.

And we only call it twice:

if (!_BuiltInIcons)
{
_UpdateBuiltInIcons();
}

if (!_BuiltInIcons)
{
_UpdateBuiltInIcons();
}

Both of which are wrapped in a null-check.

Confirmed w/ breakpoint in _UpdateBuiltInIcons() and poking around SUI (and closing/reopening SUI).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh jeez you're a step ahead of me!

for (auto& [val, name] : s_SegoeFluentIcons)
{
_BuiltInIcons.Append(make<EnumEntry>(hstring{ name }, box_value(val)));
}
Comment on lines +133 to +136
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend the "first construct a std::vector, then move it into an IVector" approach here given that you append a lot of items.

BTW it's a funny thought but technically it's completely 100% possible to create a custom HSTRING that does not the need a heap allocation. The HSTRING struct is part of the ABI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

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);
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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
Loading