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

Allow editing font axes in the Settings UI #16104

Merged
merged 31 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5a64ea5
editing/deleting works
PankajBhojwani Oct 4, 2023
267a6c9
discard changes fixed
PankajBhojwani Oct 5, 2023
eac2fd2
spelling and format
PankajBhojwani Oct 5, 2023
4fffd25
actually fix spelling this time
PankajBhojwani Oct 5, 2023
0091390
combo box for the keys with a source of common font axis keys
PankajBhojwani Oct 5, 2023
3aaac0e
spell/format again
PankajBhojwani Oct 6, 2023
ec43d6f
Merge branch 'main' of https://github.com/microsoft/terminal into dev…
PankajBhojwani Oct 19, 2023
7ebee5d
Merge branch 'main' of https://github.com/microsoft/terminal into dev…
PankajBhojwani Jan 8, 2024
35a719c
note some todos
PankajBhojwani Jan 11, 2024
db2523e
works with some stretch goals for the future
PankajBhojwani Jan 25, 2024
f9e4254
actually use the localeindex, remove unneeded resources
PankajBhojwani Jan 25, 2024
076647b
spelling things
PankajBhojwani Jan 25, 2024
40876a1
comment and another unnecessary resources
PankajBhojwani Jan 25, 2024
1adc8e1
remove negative margins
PankajBhojwani Jan 25, 2024
1c34419
initialize name betteR
PankajBhojwani Jan 26, 2024
97dbaa0
format
PankajBhojwani Jan 26, 2024
038a5fd
logic for disabling/enabling the entire setting container and the add…
PankajBhojwani Jan 29, 2024
40996da
update comments
PankajBhojwani Jan 29, 2024
d3363e3
reset button
PankajBhojwani Jan 30, 2024
3f3885e
preview control works
PankajBhojwani Jan 30, 2024
8c035b0
comments, cleanup
PankajBhojwani Jan 30, 2024
c4f94ae
minor nit
PankajBhojwani Jan 30, 2024
ce966b8
switch to wil
PankajBhojwani Feb 1, 2024
becca2a
build std map first
PankajBhojwani Feb 1, 2024
0edfe2e
another round of nits
PankajBhojwani Feb 23, 2024
42e5341
conflict
PankajBhojwani Feb 23, 2024
a68d5a9
every time
PankajBhojwani Feb 23, 2024
4982f2c
std map first, cleanup hanging empty struct in json
PankajBhojwani Feb 28, 2024
42677ff
hstring builder
PankajBhojwani Feb 28, 2024
610ffa4
cleanup nesting
PankajBhojwani Feb 28, 2024
5f527f7
fix conflict
PankajBhojwani Feb 28, 2024
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/allow/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ CMMI
copyable
Counterintuitively
CtrlDToClose
CVS
CUI
cybersecurity
dalet
Expand Down
315 changes: 315 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Appearances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "pch.h"
#include "Appearances.h"
#include "Appearances.g.cpp"
#include "AxisKeyValuePair.g.cpp"
#include "EnumEntry.h"

#include <LibraryResources.h>
Expand Down Expand Up @@ -42,6 +43,157 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _hasPowerlineCharacters.value_or(false);
}

Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> Font::FontAxesTagsAndNames()
lhecker marked this conversation as resolved.
Show resolved Hide resolved
{
if (!_fontAxesTagsAndNames)
{
_fontAxesTagsAndNames = winrt::single_threaded_map<winrt::hstring, winrt::hstring>();
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved

wil::com_ptr<IDWriteFont> font;
THROW_IF_FAILED(_family->GetFont(0, font.put()));
wil::com_ptr<IDWriteFontFace> fontFace;
THROW_IF_FAILED(font->CreateFontFace(fontFace.put()));
wil::com_ptr<IDWriteFontFace5> fontFace5;
if (fontFace5 = fontFace.try_query<IDWriteFontFace5>())
{
wil::com_ptr<IDWriteFontResource> fontResource;
THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.put()));

std::vector<DWRITE_FONT_AXIS_VALUE> axesVector;
const auto axesCount = fontFace5->GetFontAxisValueCount();
if (axesCount > 0)
{
uint32_t localeIndex;
BOOL localeExists;
wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
const auto localeToTry = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) ? localeName : L"en-US";

axesVector.resize(axesCount);
fontFace5->GetFontAxisValues(axesVector.data(), axesCount);
for (uint32_t i = 0; i < axesCount; ++i)
{
const auto tagString = _axisTagToString(axesVector[i].axisTag);

wil::com_ptr<IDWriteLocalizedStrings> names;
THROW_IF_FAILED(fontResource->GetAxisNames(i, names.put()));

if (!SUCCEEDED(names->FindLocaleName(localeToTry, &localeIndex, &localeExists)) || !localeExists)
{
// default to the first locale in the list
localeIndex = 0;
}

uint32_t length;
if (SUCCEEDED(names->GetStringLength(localeIndex, &length)))
{
// it is reasonable to assume that the name length is not going to exceed 512 chars
wchar_t name[512];
if (SUCCEEDED(names->GetString(localeIndex, name, length + 1)))
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{
_fontAxesTagsAndNames.Insert(tagString, winrt::hstring{ name });
continue;
}
}
// if there was no name found, it means the font does not actually support this axis
// don't insert anything into the vector in this case
}
}
}
}
return _fontAxesTagsAndNames;
}

winrt::hstring Font::_axisTagToString(DWRITE_FONT_AXIS_TAG tag)
{
std::wstring result;
for (int i = 0; i < 4; ++i)
{
result.push_back((tag >> (i * 8)) & 0xFF);
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
}
return winrt::hstring{ result };
}

AxisKeyValuePair::AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap) :
_AxisKey{ axisKey },
_AxisValue{ axisValue },
_baseMap{ baseMap },
_tagToNameMap{ tagToNameMap }
{
if (_tagToNameMap.HasKey(_AxisKey))
{
int32_t i{ 0 };
// IMap guarantees that the iteration order is the same every time
// so this conversion of key to index is safe
for (const auto tagAndName : _tagToNameMap)
{
if (tagAndName.Key() == _AxisKey)
{
_AxisIndex = i;
break;
}
++i;
}
}
}

winrt::hstring AxisKeyValuePair::AxisKey()
{
return _AxisKey;
}

float AxisKeyValuePair::AxisValue()
{
return _AxisValue;
}

int32_t AxisKeyValuePair::AxisIndex()
{
return _AxisIndex;
}

void AxisKeyValuePair::AxisValue(float axisValue)
{
if (axisValue != _AxisValue)
{
_baseMap.Remove(_AxisKey);
_AxisValue = axisValue;
_baseMap.Insert(_AxisKey, _AxisValue);
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AxisValue" });
}
}

void AxisKeyValuePair::AxisKey(winrt::hstring axisKey)
{
if (axisKey != _AxisKey)
{
_baseMap.Remove(_AxisKey);
_AxisKey = axisKey;
_baseMap.Insert(_AxisKey, _AxisValue);
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AxisKey" });
}
}

void AxisKeyValuePair::AxisIndex(int32_t axisIndex)
{
if (axisIndex != _AxisIndex)
{
_AxisIndex = axisIndex;

int32_t i{ 0 };
// same as in the constructor, iterating through IMap
// gives us the same order every time
for (const auto tagAndName : _tagToNameMap)
{
if (i == _AxisIndex)
{
AxisKey(tagAndName.Key());
break;
}
++i;
}
}
}

AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) :
_appearance{ appearance }
{
Expand All @@ -60,8 +212,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// box, prevent it from ever being changed again.
_NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible");
}
else if (viewModelProperty == L"FontAxes")
{
// this is a weird one
// we manually make the observable vector based on the map in the settings model
// (this is due to xaml being unable to bind a list view to a map)
// so when the FontAxes change (say from the reset button), reinitialize the observable vector
InitializeFontAxesVector();
}
});

InitializeFontAxesVector();

// Cache the original BG image path. If the user clicks "Use desktop
// wallpaper", then un-checks it, this is the string we'll restore to
// them.
Expand Down Expand Up @@ -205,6 +367,115 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
LightColorSchemeName(val.Name());
}

void AppearanceViewModel::AddNewAxisKeyValuePair()
{
if (!_appearance.SourceProfile().FontInfo().FontAxes())
{
_appearance.SourceProfile().FontInfo().FontAxes(winrt::single_threaded_map<winrt::hstring, float>());
}
auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes();

// find one axis that does not already exist, and add that
// if there are no more possible axes to add, the button is disabled so there shouldn't be a way to get here
const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames();
for (const auto tagAndName : possibleAxesTagsAndNames)
{
if (!fontAxesMap.HasKey(tagAndName.Key()))
{
fontAxesMap.Insert(tagAndName.Key(), gsl::narrow<float>(0));
FontAxesVector().Append(_CreateAxisKeyValuePairHelper(tagAndName.Key(), gsl::narrow<float>(0), fontAxesMap, possibleAxesTagsAndNames));
break;
}
}
_NotifyChanges(L"CanFontAxesBeAdded");
}

void AppearanceViewModel::DeleteAxisKeyValuePair(winrt::hstring key)
{
for (uint32_t i = 0; i < _FontAxesVector.Size(); i++)
{
if (_FontAxesVector.GetAt(i).AxisKey() == key)
{
FontAxesVector().RemoveAt(i);
_appearance.SourceProfile().FontInfo().FontAxes().Remove(key);
break;
}
}
_NotifyChanges(L"CanFontAxesBeAdded");
}

void AppearanceViewModel::InitializeFontAxesVector()
{
if (!_FontAxesVector)
{
_FontAxesVector = winrt::single_threaded_observable_vector<Editor::AxisKeyValuePair>();
}
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved

_FontAxesVector.Clear();
if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
{
const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames();
for (const auto axis : fontAxesMap)
{
// only show the axes that the font supports
// any axes that the font doesn't support continue to be stored in the json, we just don't show them in the UI
if (fontAxesTagToNameMap.HasKey(axis.Key()))
{
_FontAxesVector.Append(_CreateAxisKeyValuePairHelper(axis.Key(), axis.Value(), fontAxesMap, fontAxesTagToNameMap));
}
}
}
_NotifyChanges(L"AreFontAxesAvailable", L"CanFontAxesBeAdded");
}

// Method Description:
// - Determines whether the currently selected font has any variable font axes
bool AppearanceViewModel::AreFontAxesAvailable()
{
return ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames().Size() > 0;
}

// Method Description:
// - Determines whether the currently selected font has any variable font axes that have not already been set
bool AppearanceViewModel::CanFontAxesBeAdded()
{
if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0)
{
if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
{
for (const auto tagAndName : fontAxesTagToNameMap)
{
if (!fontAxesMap.HasKey(tagAndName.Key()))
{
// we found an axis that has not been set
return true;
}
}
// all possible axes have been set already
return false;
}
// the font supports font axes but the profile has none set
return true;
}
// the font does not support any font axes
return false;
}

// Method Description:
// - Creates an AxisKeyValuePair and sets up an event handler for it
Editor::AxisKeyValuePair AppearanceViewModel::_CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap)
{
const auto axisKeyValuePair = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::AxisKeyValuePair>(axisKey, axisValue, baseMap, tagToNameMap);
// when either the key or the value changes, send an event for the preview control to catch
axisKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& /*sender*/, auto& /*e*/) {
if (auto appVM{ weakThis.get() })
{
appVM->_NotifyChanges(L"AxisKeyValuePair");
}
});
return axisKeyValuePair;
}

DependencyProperty Appearances::_AppearanceProperty{ nullptr };

Appearances::Appearances() :
Expand Down Expand Up @@ -271,6 +542,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto backgroundImgCheckboxTooltip{ ToolTipService::GetToolTip(UseDesktopImageCheckBox()) };
Automation::AutomationProperties::SetFullDescription(UseDesktopImageCheckBox(), unbox_value<hstring>(backgroundImgCheckboxTooltip));

_FontAxesNames = winrt::single_threaded_observable_vector<winrt::hstring>();
FontAxesNamesCVS().Source(_FontAxesNames);

INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content");
}

Expand Down Expand Up @@ -330,6 +604,28 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
ShowProportionalFontWarning(false);
}

_FontAxesNames.Clear();
const auto axesTagsAndNames = newFontFace.FontAxesTagsAndNames();
for (const auto tagAndName : axesTagsAndNames)
{
_FontAxesNames.Append(tagAndName.Value());
}

// when the font face changes, we have to tell the view model to update the font axes vector
// since the new font may not have the same possible axes as the previous one
Appearance().InitializeFontAxesVector();
if (!Appearance().AreFontAxesAvailable())
{
// if the previous font had available font axes and the expander was expanded,
// at this point the expander would be set to disabled so manually collapse it
FontAxesContainer().SetExpanded(false);
FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text"));
}
else
{
FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text"));
}
Comment on lines +619 to +629
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was something extra I added - we could just set the entire expander to invisible if the font does not support font axes, but I figured it would be more consistent for the user if we still show the expander and just set it to disabled instead. If we feel like this is more trouble than its worth I'm happy to switch to the invisible implementation instead.

}

void Appearances::_ViewModelChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/)
Expand All @@ -348,6 +644,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
biButton.IsChecked(biButton.Tag().as<int32_t>() == biAlignmentVal);
}

FontAxesCVS().Source(Appearance().FontAxesVector());
Appearance().AreFontAxesAvailable() ? FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")) : FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text"));

_ViewModelChangedRevoker = Appearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
const auto settingName{ args.PropertyName() };
if (settingName == L"CursorShape")
Expand Down Expand Up @@ -470,6 +769,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}

void Appearances::DeleteAxisKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
{
if (const auto& button{ sender.try_as<Controls::Button>() })
{
if (const auto& tag{ button.Tag().try_as<winrt::hstring>() })
{
Appearance().DeleteAxisKeyValuePair(tag.value());
}
}
}

void Appearances::AddNewAxisKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
{
Appearance().AddNewAxisKeyValuePair();
}

bool Appearances::IsVintageCursor() const
{
return Appearance().CursorShape() == Core::CursorStyle::Vintage;
Expand Down
Loading
Loading