Add tab color setting to settings UI (#19351)

## Summary of the Pull Request
Adds the tab color profile setting to the settings UI. It's positioned
next to the tab title at the root of the profile page.

The new component uses a nullable color picker control to allow the user
to pick a color. The null color is represented as "Use theme color".

The tricky part is evaluating the `ThemeColor` for `null` (aka "use
theme color"). Since the value is dependent on the active theme, it can
be any of the following values:
- theme.tab.background...
   - explicit color
   - accent color
   - terminal background color
- (if no theme.tab.background is defined) theme.window.applicationTheme
   - light --> #F9F9F9
   - dark --> #282828
- default --> one of the above two values depending on the application
theme

The above light/dark values were acquired by using the color picker on
the tab when in light/dark theme.

## Validation Steps Performed
 accessible value is read out
 explicit tab color set
- tab color is null, so we fall back to...
-  theme.tab.background: explicit color, accent color, terminal
background color
-  theme.window.applicationTheme (and no theme.tab.background defined):
light, dark, default (aka not defined)
      -  updates when theme is changed locally and via JSON

## PR Checklist
Closes part of #18318
This commit is contained in:
Carlos Zamora 2025-09-25 11:37:43 -07:00 committed by GitHub
parent 6b428577b9
commit ad6473d6ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 122 additions and 4 deletions

View File

@ -152,6 +152,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_NotifyChanges(L"AnswerbackMessagePreview");
}
else if (viewModelProperty == L"TabColor")
{
_NotifyChanges(L"TabColorPreview");
}
else if (viewModelProperty == L"TabThemeColorPreview")
{
_NotifyChanges(L"TabColorPreview");
}
});
_defaultAppearanceViewModel.PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
const auto viewModelProperty{ args.PropertyName() };
if (viewModelProperty == L"DarkColorSchemeName" || viewModelProperty == L"LightColorSchemeName")
{
_NotifyChanges(L"TabThemeColorPreview");
}
});
// Do the same for the starting directory
@ -438,7 +454,85 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return RS_(L"Profile_AnswerbackMessageNone");
}
Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance()
Windows::UI::Color ProfileViewModel::TabColorPreview() const
{
if (const auto modelVal = _profile.TabColor())
{
const auto color = modelVal.Value();
// user defined an override value
return Windows::UI::Color{
.A = 255,
.R = color.R,
.G = color.G,
.B = color.B
};
}
// set to null --> deduce value from theme
return TabThemeColorPreview();
}
Windows::UI::Color ProfileViewModel::TabThemeColorPreview() const
{
const auto currentTheme = _appSettings.GlobalSettings().CurrentTheme();
if (const auto tabTheme = currentTheme.Tab())
{
// theme.tab.background: theme color must be evaluated
if (const auto tabBackground = tabTheme.Background())
{
const auto& tabBrush = tabBackground.Evaluate(Application::Current().Resources(),
Windows::UI::Xaml::Media::SolidColorBrush{ DefaultAppearance().CurrentColorScheme().BackgroundColor().Color() },
false);
if (const auto& tabColorBrush = tabBrush.try_as<Windows::UI::Xaml::Media::SolidColorBrush>())
{
const auto brushColor = tabColorBrush.Color();
return brushColor;
}
}
}
else if (const auto windowTheme = currentTheme.Window())
{
// theme.window.applicationTheme: evaluate light/dark to XAML default tab color
// Can also be "Default", in which case we fall through below
const auto appTheme = windowTheme.RequestedTheme();
if (appTheme == ElementTheme::Dark)
{
return Windows::UI::Color{
.A = 0xFF,
.R = 0x28,
.G = 0x28,
.B = 0x28
};
}
else if (appTheme == ElementTheme::Light)
{
return Windows::UI::Color{
.A = 0xFF,
.R = 0xF9,
.G = 0xF9,
.B = 0xF9
};
}
}
// XAML default tab color
if (Model::Theme::IsSystemInDarkTheme())
{
return Windows::UI::Color{
.A = 0xFF,
.R = 0x28,
.G = 0x28,
.B = 0x28
};
}
return Windows::UI::Color{
.A = 0xFF,
.R = 0xF9,
.G = 0xF9,
.B = 0xF9
};
}
Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance() const
{
return _defaultAppearanceViewModel;
}
@ -477,7 +571,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance");
}
Editor::AppearanceViewModel ProfileViewModel::UnfocusedAppearance()
Editor::AppearanceViewModel ProfileViewModel::UnfocusedAppearance() const
{
return _unfocusedAppearanceViewModel;
}

View File

@ -112,8 +112,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// general profile knowledge
winrt::guid OriginalProfileGuid() const noexcept;
bool CanDeleteProfile() const;
Editor::AppearanceViewModel DefaultAppearance();
Editor::AppearanceViewModel UnfocusedAppearance();
Editor::AppearanceViewModel DefaultAppearance() const;
Editor::AppearanceViewModel UnfocusedAppearance() const;
bool HasUnfocusedAppearance();
bool EditableUnfocusedAppearance() const noexcept;
bool ShowUnfocusedAppearance();
@ -127,6 +127,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool Orphaned() const;
hstring TabTitlePreview() const;
hstring AnswerbackMessagePreview() const;
Windows::UI::Color TabColorPreview() const;
Windows::UI::Color TabThemeColorPreview() const;
til::typed_event<Editor::ProfileViewModel, Editor::DeleteProfileEventArgs> DeleteProfileRequested;

View File

@ -121,6 +121,8 @@ namespace Microsoft.Terminal.Settings.Editor
String TabTitlePreview { get; };
String AnswerbackMessagePreview { get; };
Windows.UI.Color TabColorPreview { get; };
Windows.UI.Color TabThemeColorPreview { get; };
void CreateUnfocusedAppearance();
void DeleteUnfocusedAppearance();

View File

@ -208,6 +208,22 @@
Text="{x:Bind Profile.TabTitle, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Tab Color -->
<local:SettingContainer x:Name="TabColor"
x:Uid="Profile_TabColor"
ClearSettingValue="{x:Bind Profile.ClearTabColor}"
CurrentValue="{x:Bind Profile.TabColorPreview, Mode=OneWay}"
CurrentValueAccessibleName="{x:Bind Profile.TabColorPreview, Converter={StaticResource ColorToStringConverter}, Mode=OneWay}"
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Profile.HasTabColor, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.TabColorOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
<local:NullableColorPicker x:Uid="Profile_TabColor_NullableColorPicker"
ColorSchemeVM="{x:Bind Profile.DefaultAppearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Profile.TabColor, Mode=TwoWay}"
NullColorPreview="{x:Bind Profile.TabThemeColorPreview, Mode=OneWay}" />
</local:SettingContainer>
<!-- Elevate -->
<local:SettingContainer x:Uid="Profile_Elevate"
ClearSettingValue="{x:Bind Profile.ClearElevate}"

View File

@ -2080,6 +2080,10 @@
<value>Use scheme color</value>
<comment>Label for a button directing the user to use the selection background color defined in the terminal's current color scheme.</comment>
</data>
<data name="Profile_TabColor_NullableColorPicker.NullColorButtonLabel" xml:space="preserve">
<value>Use theme color</value>
<comment>Label for a button directing the user to use the tab color defined in the terminal's current theme.</comment>
</data>
<data name="Profile_IconTypeNone" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "icon style" dropdown. When selected, there will be no icon for the profile.</comment>