Skip to content

Commit

Permalink
Detect likely Powerline fonts, add a special powerline preview (#15365)
Browse files Browse the repository at this point in the history
When we detect a font that has a glyph for `U+E0B6`, we will switch the
preview connection text to contain a special powerline prompt. This will
allow people to see how different settings might impact their real-world
environment.

When we _don't_ detect such support, we fall back to the CMD-style
`C:\>` prompt.

Pros:
- It's beautiful.

Cons:
- More code

Risks:
- `U+E0B6` is part of the private use area, and fonts that have symbols
there (such as Cirth as sub-allocated by the ConScript Unicode Registry)
will result in something unexpected.
- Actually, `E0B6` isn't part of base powerline... but I think this
specific set of characters looks too good to pass up.

(cherry picked from commit 37e8aff)
Service-Card-Id: 89468351
Service-Version: 1.18
  • Loading branch information
DHowett committed Jul 27, 2023
1 parent 1733c80 commit c0b2950
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spelling/allow/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ ok'd
overlined
pipeline
postmodern
Powerline
powerline
ptys
qof
qps
Expand Down
40 changes: 23 additions & 17 deletions src/cascadia/TerminalSettingsEditor/Appearances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
bool Font::HasPowerlineCharacters()
{
if (!_hasPowerlineCharacters.has_value())
{
try
{
winrt::com_ptr<IDWriteFont> font;
THROW_IF_FAILED(_family->GetFont(0, font.put()));
BOOL exists{};
// We're actually checking for the "Extended" PowerLine glyph set.
// They're more fun.
THROW_IF_FAILED(font->HasCharacter(0xE0B6, &exists));
_hasPowerlineCharacters = (exists == TRUE);
}
catch (...)
{
_hasPowerlineCharacters = false;
}
}
return _hasPowerlineCharacters.value_or(false);
}

AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) :
_appearance{ appearance }
{
Expand Down Expand Up @@ -288,25 +310,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation

IInspectable Appearances::CurrentFontFace() const
{
// look for the current font in our shown list of fonts
const auto& appearanceVM{ Appearance() };
const auto appearanceFontFace{ appearanceVM.FontFace() };
const auto& currentFontList{ ShowAllFonts() ? ProfileViewModel::CompleteFontList() : ProfileViewModel::MonospaceFontList() };
IInspectable fallbackFont;
for (const auto& font : currentFontList)
{
if (font.LocalizedName() == appearanceFontFace)
{
return box_value(font);
}
else if (font.LocalizedName() == L"Cascadia Mono")
{
fallbackFont = box_value(font);
}
}

// we couldn't find the desired font, set to "Cascadia Mono" since that ships by default
return fallbackFont;
return box_value(ProfileViewModel::FindFontWithLocalizedName(appearanceFontFace));
}

void Appearances::FontFace_SelectionChanged(const IInspectable& /*sender*/, const SelectionChangedEventArgs& e)
Expand Down
12 changes: 10 additions & 2 deletions src/cascadia/TerminalSettingsEditor/Appearances.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct Font : FontT<Font>
{
public:
Font(std::wstring name, std::wstring localizedName) :
Font(std::wstring name, std::wstring localizedName, IDWriteFontFamily* family) :
_Name{ name },
_LocalizedName{ localizedName } {};
_LocalizedName{ localizedName }
{
_family.copy_from(family);
}

hstring ToString() { return _LocalizedName; }
bool HasPowerlineCharacters();

WINRT_PROPERTY(hstring, Name);
WINRT_PROPERTY(hstring, LocalizedName);

private:
winrt::com_ptr<IDWriteFontFamily> _family;
std::optional<bool> _hasPowerlineCharacters;
};

struct AppearanceViewModel : AppearanceViewModelT<AppearanceViewModel>, ViewModelHelper<AppearanceViewModel>
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsEditor/Appearances.idl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Microsoft.Terminal.Settings.Editor
{
String Name { get; };
String LocalizedName { get; };
Boolean HasPowerlineCharacters { get; };
}

runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
Expand Down
20 changes: 17 additions & 3 deletions src/cascadia/TerminalSettingsEditor/PreviewConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;

static constexpr std::wstring_view PromptTextPlain{ L"C:\\> " };
static constexpr std::wstring_view PromptTextPowerline{ L"\x1b[49;34m\xe0b6\x1b[1;97;44m C:\\ \x1b[m\x1b[46;34m\xe0b8\x1b[49;36m\xe0b8\x1b[m " };

// clang-format off
static constexpr std::wstring_view PreviewText{
L"\x001b"
L"c" // Hard Reset (RIS); on separate lines to avoid becoming 0x01BC
L"Windows Terminal\r\n"
L"C:\\> \x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
L"{0}\x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
L"\x1b[1m" L"diff --git a/win b/win\x1b[m\r\n"
L"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
L"\x1b[31m- Windows Console\x1b[m\r\n"
L"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
L"C:\\> \x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
L"{0}\x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
};
// clang-format on

Expand All @@ -27,7 +32,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Start() noexcept
{
// Send the preview text
_TerminalOutputHandlers(PreviewText);
_TerminalOutputHandlers(fmt::format(PreviewText, _displayPowerlineGlyphs ? PromptTextPowerline : PromptTextPlain));
}

void PreviewConnection::Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) noexcept
Expand All @@ -45,4 +50,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Close() noexcept
{
}

void PreviewConnection::DisplayPowerlineGlyphs(bool d) noexcept
{
if (_displayPowerlineGlyphs != d)
{
_displayPowerlineGlyphs = d;
Start();
}
}
}
5 changes: 5 additions & 0 deletions src/cascadia/TerminalSettingsEditor/PreviewConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Resize(uint32_t rows, uint32_t columns) noexcept;
void Close() noexcept;

void DisplayPowerlineGlyphs(bool d) noexcept;

winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; }

WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, IInspectable);

private:
bool _displayPowerlineGlyphs{ false };
};
}
41 changes: 34 additions & 7 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family);

Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_FontList{ nullptr };

Expand Down Expand Up @@ -118,12 +120,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
com_ptr<IDWriteFontFamily> fontFamily;
THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put()));

// get the font's localized names
com_ptr<IDWriteLocalizedStrings> localizedFamilyNames;
THROW_IF_FAILED(fontFamily->GetFamilyNames(localizedFamilyNames.put()));

// construct a font entry for tracking
if (const auto fontEntry{ _GetFont(localizedFamilyNames) })
if (const auto fontEntry{ _FontObjectForDWriteFont(fontFamily.get()) })
{
// check if the font is monospaced
try
Expand Down Expand Up @@ -159,7 +157,32 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
CATCH_LOG();

Editor::Font ProfileViewModel::_GetFont(com_ptr<IDWriteLocalizedStrings> localizedFamilyNames)
Editor::Font ProfileViewModel::FindFontWithLocalizedName(const winrt::hstring& name) noexcept
{
// look for the current font in our shown list of fonts
Editor::Font fallbackFont{ nullptr };
try
{
const auto& currentFontList{ CompleteFontList() };
for (const auto& font : currentFontList)
{
if (font.LocalizedName() == name)
{
return font;
}
else if (font.LocalizedName() == L"Cascadia Mono")
{
fallbackFont = font;
}
}
}
CATCH_LOG();

// we couldn't find the desired font, set to "Cascadia Mono" if we found that since it ships by default
return fallbackFont;
}

static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family)
{
// used for the font's name as an identifier (i.e. text block's font family property)
std::wstring nameID;
Expand All @@ -169,6 +192,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
std::wstring localizedName;
UINT32 localizedNameIndex;

// get the font's localized names
winrt::com_ptr<IDWriteLocalizedStrings> localizedFamilyNames;
THROW_IF_FAILED(family->GetFamilyNames(localizedFamilyNames.put()));

// use our current locale to find the localized name
auto exists{ FALSE };
HRESULT hr;
Expand Down Expand Up @@ -211,7 +238,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation

if (!nameID.empty() && !localizedName.empty())
{
return make<Font>(nameID, localizedName);
return make<Font>(nameID, localizedName, family);
}
return nullptr;
}
Expand Down
3 changes: 1 addition & 2 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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 Editor::Font FindFontWithLocalizedName(winrt::hstring const& name) noexcept;

ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings);
Model::TerminalSettings TermSettings() const;
Expand Down Expand Up @@ -123,8 +124,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;

static Editor::Font _GetFont(com_ptr<IDWriteLocalizedStrings> localizedFamilyNames);

Model::CascadiaSettings _appSettings;
Editor::AppearanceViewModel _unfocusedAppearanceViewModel;
};
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace Microsoft.Terminal.Settings.Editor
{
static Windows.Foundation.Collections.IObservableVector<Font> CompleteFontList { get; };
static Windows.Foundation.Collections.IObservableVector<Font> MonospaceFontList { get; };
static Font FindFontWithLocalizedName(String name);

Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; };

Expand Down
20 changes: 19 additions & 1 deletion src/cascadia/TerminalSettingsEditor/Profiles_Appearance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Profiles_Appearance::Profiles_Appearance()
{
InitializeComponent();
_previewConnection = winrt::make_self<PreviewConnection>();
}

void Profiles_Appearance::OnNavigatedTo(const NavigationEventArgs& e)
Expand All @@ -36,7 +37,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (!_previewControl)
{
const auto settings = _Profile.TermSettings();
_previewControl = Control::TermControl(settings, settings, make<PreviewConnection>());
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
_previewControl = Control::TermControl(settings, settings, *_previewConnection);
_previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
_previewControl.DisplayCursorWhileBlurred(true);
Expand Down Expand Up @@ -68,9 +70,25 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_Profile.DeleteUnfocusedAppearance();
}

bool Profiles_Appearance::_looksLikePowerlineFont() const
{
if (_Profile && _Profile.DefaultAppearance())
{
if (const auto fontName = _Profile.DefaultAppearance().FontFace(); !fontName.empty())
{
if (const auto font = ProfileViewModel::FindFontWithLocalizedName(fontName))
{
return font.HasPowerlineCharacters();
}
}
}
return false;
}

void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const
{
const auto settings = _Profile.TermSettings();
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
_previewControl.UpdateControlSettings(settings, settings);
}
}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Appearance.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "Profiles_Appearance.g.h"
#include "Utils.h"
#include "PreviewConnection.h"

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Expand All @@ -26,7 +27,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation

private:
void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const;
bool _looksLikePowerlineFont() const;

winrt::com_ptr<PreviewConnection> _previewConnection{ nullptr };
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
Expand Down

0 comments on commit c0b2950

Please sign in to comment.