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

Apply MVVM for global settings in SUI #11853

Closed
wants to merge 5 commits into from
Closed
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
190 changes: 2 additions & 188 deletions src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,205 +2,19 @@
// Licensed under the MIT license.

#include "pch.h"
#include "EnumEntry.h"
#include "GlobalAppearance.h"
#include "GlobalAppearance.g.cpp"
#include "GlobalAppearancePageNavigationState.g.cpp"

#include <LibraryResources.h>
#include <WtExeUtils.h>

using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Windows::Foundation::Collections;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// For ComboBox an empty SelectedItem string denotes no selection.
// What we want instead is for "Use system language" to be selected by default.
// --> "und" is synonymous for "Use system language".
constexpr std::wstring_view systemLanguageTag{ L"und" };

GlobalAppearance::GlobalAppearance()
{
InitializeComponent();

INITIALIZE_BINDABLE_ENUM_SETTING(Theme, ElementTheme, winrt::Windows::UI::Xaml::ElementTheme, L"Globals_Theme", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(TabWidthMode, TabViewWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, L"Globals_TabWidthMode", L"Content");
}

void GlobalAppearance::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::GlobalAppearancePageNavigationState>();
}

winrt::hstring GlobalAppearance::LanguageDisplayConverter(const winrt::hstring& tag)
{
if (tag == systemLanguageTag)
{
return RS_(L"Globals_LanguageDefault");
}

winrt::Windows::Globalization::Language language{ tag };
return language.NativeName();
}

// Returns whether the language selector is available/shown.
//
// winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride()
// doesn't work for unpackaged applications. The corresponding code in TerminalApp is disabled.
// It would be confusing for our users if we presented a dysfunctional language selector.
bool GlobalAppearance::LanguageSelectorAvailable()
{
return IsPackaged();
}

// Returns the list of languages the user may override the application language with.
// The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}.
// "und" is short for "undefined" and is synonymous for "Use system language" in this code.
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> GlobalAppearance::LanguageList()
{
if (_languageList)
{
return _languageList;
}

if (!LanguageSelectorAvailable())
{
_languageList = {};
return _languageList;
}

// In order to return the language list this code does the following:
// [1] Get all possible languages we want to allow the user to choose.
// We have to acquire languages from multiple sources, creating duplicates. See below at [1].
// [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order.
// I wanted to sort the localized language names initially, but it turned out to be complex.
// [3] Remove potential duplicates in our language list from [1].
// We don't want to have en-US twice in the list, do we?
// [4] Optionally remove unwanted language tags (like pseudo-localizations).

std::vector<winrt::hstring> tags;

// [1]:
{
// ManifestLanguages contains languages the app ships with.
//
// Languages is a computed list that merges the ManifestLanguages with the
// user's ranked list of preferred languages taken from the system settings.
// As is tradition the API documentation is incomplete though, as it can also
// contain regional language variants. If our app supports en-US, but the user
// has en-GB or en-DE in their system's preferred language list, Languages will
// contain those as well, as they're variants from a supported language. We should
// allow a user to select those, as regional formattings can vary significantly.
const std::array tagSources{
winrt::Windows::Globalization::ApplicationLanguages::ManifestLanguages(),
winrt::Windows::Globalization::ApplicationLanguages::Languages()
};

// tags will hold all the flattened results from tagSources.
// We resize() the vector to the proper size in order to efficiently GetMany() all items.
tags.resize(std::accumulate(
tagSources.begin(),
tagSources.end(),
// tags[0] will be "und" - the "Use system language" item
// tags[1..n] will contain tags from tagSources.
// --> totalTags is offset by 1
1,
[](uint32_t sum, const auto& v) -> uint32_t {
return sum + v.Size();
}));

// As per the function definition, the first item
// is always "Use system language" ("und").
auto data = tags.data();
*data++ = systemLanguageTag;

// Finally GetMany() all the tags from tagSources.
for (const auto& v : tagSources)
{
const auto size = v.Size();
v.GetMany(0, winrt::array_view(data, size));
data += size;
}
}

// NOTE: The size of tags is always >0, due to tags[0] being hardcoded to "und".
const auto tagsBegin = ++tags.begin();
const auto tagsEnd = tags.end();

// [2]:
std::sort(tagsBegin, tagsEnd);

// I'd love for both, std::unique and std::remove_if, to occur in a single loop,
// but the code turned out to be complex and even less maintainable, so I gave up.
{
// [3] part 1:
auto it = std::unique(tagsBegin, tagsEnd);

// The qps- languages are useful for testing ("pseudo-localization").
// --> Leave them in if debug features are enabled.
if (!_State.Globals().DebugFeaturesEnabled())
{
// [4] part 1:
it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool {
return til::starts_with(tag, L"qps-");
});
}

// [3], [4] part 2 (completing the so called "erase-remove idiom"):
tags.erase(it, tagsEnd);
}

_languageList = winrt::single_threaded_observable_vector(std::move(tags));
return _languageList;
}

winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentLanguage()
{
if (_currentLanguage)
{
return _currentLanguage;
}

if (!LanguageSelectorAvailable())
{
_currentLanguage = {};
return _currentLanguage;
}

// NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
auto currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (currentLanguage.empty())
{
currentLanguage = systemLanguageTag;
}

_currentLanguage = winrt::box_value(currentLanguage);
return _currentLanguage;
}

void GlobalAppearance::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag)
{
_currentLanguage = tag;

const auto currentLanguage = winrt::unbox_value<winrt::hstring>(_currentLanguage);
const auto globals = _State.Globals();
if (currentLanguage == systemLanguageTag)
{
globals.ClearLanguage();
}
else
{
globals.Language(currentLanguage);
}
}

bool GlobalAppearance::FeatureNotificationIconEnabled() const noexcept
void GlobalAppearance::OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e)
{
return Feature_NotificationIcon::IsEnabled();
_Globals = e.Parameter().as<Editor::GlobalAppearancePageNavigationState>().Globals();
}
}
25 changes: 3 additions & 22 deletions src/cascadia/TerminalSettingsEditor/GlobalAppearance.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct GlobalAppearancePageNavigationState : GlobalAppearancePageNavigationStateT<GlobalAppearancePageNavigationState>
{
public:
GlobalAppearancePageNavigationState(const Model::GlobalAppSettings& settings) :
GlobalAppearancePageNavigationState(const Editor::GlobalSettingsViewModel& settings) :
_Globals{ settings } {}

WINRT_PROPERTY(Model::GlobalAppSettings, Globals, nullptr)
WINRT_PROPERTY(Editor::GlobalSettingsViewModel, Globals, nullptr)
};

struct GlobalAppearance : public HasScrollViewer<GlobalAppearance>, GlobalAppearanceT<GlobalAppearance>
Expand All @@ -25,26 +25,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation

void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);

bool FeatureNotificationIconEnabled() const noexcept;

WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr);
GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals(), Theme);
GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals(), TabWidthMode);

public:
// LanguageDisplayConverter maps the given BCP 47 tag to a localized string.
// For instance "en-US" produces "English (United States)", while "de-DE" produces
// "Deutsch (Deutschland)". This works independently of the user's locale.
static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag);

bool LanguageSelectorAvailable();
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> LanguageList();
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);

private:
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList;
winrt::Windows::Foundation::IInspectable _currentLanguage;
WINRT_PROPERTY(Editor::GlobalSettingsViewModel, Globals, nullptr);
};
}

Expand Down
19 changes: 3 additions & 16 deletions src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import "EnumEntry.idl";
import "GlobalSettingsViewModel.idl";

namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass GlobalAppearancePageNavigationState
{
Microsoft.Terminal.Settings.Model.GlobalAppSettings Globals;
GlobalSettingsViewModel Globals { get; };
};

[default_interface] runtimeclass GlobalAppearance : Windows.UI.Xaml.Controls.Page
{
GlobalAppearance();
GlobalAppearancePageNavigationState State { get; };

static String LanguageDisplayConverter(String tag);
Boolean LanguageSelectorAvailable { get; };
Windows.Foundation.Collections.IObservableVector<String> LanguageList { get; };
IInspectable CurrentLanguage;

IInspectable CurrentTheme;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> ThemeList { get; };

IInspectable CurrentTabWidthMode;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> TabWidthModeList { get; };

Boolean FeatureNotificationIconEnabled { get; };
GlobalSettingsViewModel Globals { get; };
}
}
36 changes: 18 additions & 18 deletions src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language"
Margin="0"
Visibility="{x:Bind LanguageSelectorAvailable}">
<ComboBox ItemsSource="{x:Bind LanguageList}"
SelectedItem="{x:Bind CurrentLanguage, Mode=TwoWay}">
Visibility="{x:Bind Globals.LanguageSelectorAvailable}">
<ComboBox ItemsSource="{x:Bind Globals.LanguageList}"
SelectedItem="{x:Bind Globals.CurrentLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind local:GlobalAppearance.LanguageDisplayConverter((x:String))}" />
<TextBlock Text="{x:Bind local:GlobalSettingsViewModel.LanguageDisplayConverter((x:String))}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Expand All @@ -44,59 +44,59 @@
<local:SettingContainer x:Uid="Globals_Theme">
<muxc:RadioButtons AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
ItemsSource="{x:Bind ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTheme, Mode=TwoWay}" />
ItemsSource="{x:Bind Globals.ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind Globals.CurrentTheme, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Always show tabs -->
<local:SettingContainer x:Uid="Globals_AlwaysShowTabs">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysShowTabs, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind Globals.AlwaysShowTabs, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Show Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitlebar">
<ToggleSwitch IsOn="{x:Bind State.Globals.ShowTabsInTitlebar, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind Globals.ShowTabsInTitlebar, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Show Acrylic in Tab Row -->
<local:SettingContainer x:Uid="Globals_AcrylicTabRow">
<ToggleSwitch IsOn="{x:Bind State.Globals.UseAcrylicInTabRow, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind Globals.UseAcrylicInTabRow, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Show Title in Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitleInTitlebar">
<ToggleSwitch IsOn="{x:Bind State.Globals.ShowTitleInTitlebar, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind Globals.ShowTitleInTitlebar, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Always on Top -->
<local:SettingContainer x:Uid="Globals_AlwaysOnTop">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysOnTop, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind Globals.AlwaysOnTop, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Tab Width Mode -->
<local:SettingContainer x:Uid="Globals_TabWidthMode">
<muxc:RadioButtons AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
ItemsSource="{x:Bind TabWidthModeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTabWidthMode, Mode=TwoWay}" />
ItemsSource="{x:Bind Globals.TabWidthModeList, Mode=OneWay}"
SelectedItem="{x:Bind Globals.CurrentTabWidthMode, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Disable Animations -->
<!-- NOTE: the UID is "DisablePaneAnimationsReversed" not "DisablePaneAnimations". See GH#9124 for more details. -->
<local:SettingContainer x:Uid="Globals_DisableAnimationsReversed">
<ToggleSwitch IsOn="{x:Bind local:Converters.InvertBoolean(State.Globals.DisableAnimations), BindBack=State.Globals.SetInvertedDisableAnimationsValue, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind local:Converters.InvertBoolean(Globals.DisableAnimations), BindBack=Globals.SetInvertedDisableAnimationsValue, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Always Show Notification Icon -->
<local:SettingContainer x:Uid="Globals_AlwaysShowNotificationIcon"
Visibility="{x:Bind FeatureNotificationIconEnabled}">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysShowNotificationIcon, Mode=TwoWay}" />
Visibility="{x:Bind Globals.FeatureNotificationIconEnabled}">
<ToggleSwitch IsOn="{x:Bind Globals.AlwaysShowNotificationIcon, Mode=TwoWay}" />
</local:SettingContainer>

<!-- Minimize To Notification Area -->
<local:SettingContainer x:Uid="Globals_MinimizeToNotificationArea"
Visibility="{x:Bind FeatureNotificationIconEnabled}">
<ToggleSwitch IsOn="{x:Bind State.Globals.MinimizeToNotificationArea, Mode=TwoWay}" />
Visibility="{x:Bind Globals.FeatureNotificationIconEnabled}">
<ToggleSwitch IsOn="{x:Bind Globals.MinimizeToNotificationArea, Mode=TwoWay}" />
</local:SettingContainer>
</StackPanel>
</ScrollViewer>
Expand Down
Loading