Compare commits

..

7 Commits

Author SHA1 Message Date
Carlos Zamora
a175b6291a fix disable handling for AlwaysShowTabs 2026-05-18 18:29:58 -07:00
Carlos Zamora
e60562d99d port chevron animation & click handling; use WCT styling 2026-05-18 17:36:50 -07:00
Carlos Zamora
96d0762a77 port over more WCT SettingsCard/Expander functionality 2026-05-18 15:06:28 -07:00
Carlos Zamora
c64b2433da code format 2026-05-15 13:28:47 -07:00
Carlos Zamora
17538eb499 Convert Navigators to SettingsCards 2026-05-15 13:28:30 -07:00
Carlos Zamora
edfc599d6d Consolidate SettingContainer styles 2026-05-15 12:50:53 -07:00
Carlos Zamora
acc1a59367 Port SettingsCard/Expander from WCT; have SettingContainer use it 2026-05-14 19:31:22 -07:00
70 changed files with 2912 additions and 1570 deletions

View File

@@ -479,7 +479,6 @@
"toggleReadOnlyMode",
"toggleShaderEffects",
"toggleSplitOrientation",
"workspaces",
"wt",
"unbound"
],
@@ -2041,11 +2040,6 @@
"unfocusedFrame": {
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
"$ref": "#/$defs/ThemeColor"
},
"showWindowsButton": {
"description": "When set to true, the workspace/windows button will be shown in the tab row.",
"type": "boolean",
"default": true
}
}
},

View File

@@ -938,27 +938,6 @@ namespace winrt::TerminalApp::implementation
co_return;
}
// Launch `wt -w <name>` so the monarch can either summon an existing
// window with that name or restore a persisted workspace.
safe_void_coroutine TerminalPage::_OpenWorkspaceWindow(const winrt::hstring name)
{
co_await winrt::resume_background();
const auto exePath{ GetWtExePath() };
const auto cmdline = fmt::format(FMT_COMPILE(L"-w {}"), std::wstring_view{ name });
SHELLEXECUTEINFOW seInfo{ 0 };
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_NOASYNC;
seInfo.lpVerb = L"open";
seInfo.lpFile = exePath.c_str();
seInfo.lpParameters = cmdline.c_str();
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
co_return;
}
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
const ActionEventArgs& actionArgs)
{
@@ -1079,7 +1058,6 @@ namespace winrt::TerminalApp::implementation
// Fun!
// WindowRenamerTextBox().Focus(FocusState::Programmatic);
_renamerLayoutUpdatedRevoker.revoke();
_renamerLayoutCount = 0;
_renamerLayoutUpdatedRevoker = WindowRenamerTextBox().LayoutUpdated(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (auto self{ weakThis.get() })
{
@@ -1655,35 +1633,4 @@ namespace winrt::TerminalApp::implementation
args.Handled(handled);
}
}
void TerminalPage::_HandleOpenWorkspace(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
// Open (or summon) a named window. We launch a new `wt -w <name>`
// process which the monarch will route to the correct live window or
// restore from a persisted workspace.
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<OpenWorkspaceArgs>())
{
const auto name = realArgs.Name();
if (!name.empty())
{
_OpenWorkspaceWindow(name);
}
args.Handled(true);
}
}
}
void TerminalPage::_HandleWorkspaces(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (_workspaceFlyout && _workspaceDropdown)
{
_workspaceFlyout.ShowAt(_workspaceDropdown);
}
args.Handled(true);
}
}

View File

@@ -92,10 +92,6 @@ INewContentArgs Pane::GetTerminalArgsForPane(BuildStartupKind kind) const
{
// Leaves are the only things that have controls
assert(_IsLeaf());
if (!_content)
{
return nullptr;
}
return _content.GetNewTerminalArgs(kind);
}

View File

@@ -85,7 +85,6 @@ namespace winrt::TerminalApp::implementation
WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::WindowLayout, PersistedLayout, nullptr);
};
}

View File

@@ -51,6 +51,5 @@ namespace TerminalApp
CommandlineArgs Command { get; };
String Content { get; };
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
Microsoft.Terminal.Settings.Model.WindowLayout PersistedLayout;
};
}

View File

@@ -743,38 +743,6 @@
<value>unnamed window</value>
<comment>text used to identify when a window hasn't been assigned a name by the user</comment>
</data>
<data name="NameThisWindowMenuItem" xml:space="preserve">
<value>Name this window…</value>
<comment>Menu item text shown when the current window has no name assigned</comment>
</data>
<data name="RenameThisWindowMenuItem" xml:space="preserve">
<value>Rename this window…</value>
<comment>Menu item text shown when the current window already has a name</comment>
</data>
<data name="WindowListUnnamedEntry" xml:space="preserve">
<value>#{0} (unnamed)</value>
<comment>{0} is the window ID number. Shown in the workspace flyout for windows that have no name assigned.</comment>
</data>
<data name="DeleteWorkspaceMenuItem" xml:space="preserve">
<value>Delete workspace?</value>
<comment>Menu item text shown in the right-click context menu on a saved workspace</comment>
</data>
<data name="OpenWorkspaceMenuItem" xml:space="preserve">
<value>Open workspace</value>
<comment>Menu item text for opening a saved workspace</comment>
</data>
<data name="ConfirmDeleteWorkspaceTitle" xml:space="preserve">
<value>Are you sure you want to delete the workspace "{0}"?</value>
<comment>{0} is the workspace name. Shown in a confirmation dialog when the user tries to delete a saved workspace.</comment>
</data>
<data name="ConfirmDeleteWorkspaceDelete" xml:space="preserve">
<value>Delete</value>
<comment>Primary button text on the delete workspace confirmation dialog</comment>
</data>
<data name="ConfirmDeleteWorkspaceCancel" xml:space="preserve">
<value>Cancel</value>
<comment>Cancel button text on the delete workspace confirmation dialog</comment>
</data>
<data name="WindowRenamer.Subtitle" xml:space="preserve">
<value>Enter a new name:</value>
</data>

View File

@@ -450,21 +450,6 @@ namespace winrt::TerminalApp::implementation
auto actions = t->BuildStartupActions(BuildStartupKind::None);
_AddPreviouslyClosedPaneOrTab(std::move(actions));
// If this is the last tab in a named window, persist the workspace
// layout now while tab content is still alive. After tab.Close()
// the pane content will be torn down by the time _RemoveTab runs.
if (_tabs.Size() == 1)
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
if (const auto layout = GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}
tab.Close();
}
@@ -486,13 +471,6 @@ namespace winrt::TerminalApp::implementation
const auto focusedTabIndex{ _GetFocusedTabIndex() };
// NOTE: Workspace persistence for named windows used to live here,
// but by the time _RemoveTab runs the pane content may already be
// torn down (e.g. from the close-pane path). Instead, workspace
// saves are handled earlier:
// - Close-pane (last pane): in _HandleClosePaneRequested
// - Close-tab: in _HandleCloseTabRequested
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
tab.Shutdown();
@@ -820,28 +798,6 @@ namespace winrt::TerminalApp::implementation
}
_AddPreviouslyClosedPaneOrTab(std::move(state.args));
// If this is the last pane on the last tab of a named window, persist
// the workspace layout now while the pane content is still alive.
// We can't wait until _RemoveTab, because pane->Close() below will
// destroy the content before _RemoveTab is reached.
if (_tabs.Size() == 1)
{
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (activeTab->GetLeafPaneCount() == 1)
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
if (const auto layout = GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}
}
}
// If specified, detach before closing to directly update the pane structure
pane->Close();
}

View File

@@ -25,15 +25,6 @@ namespace winrt::TerminalApp::implementation
InitializeComponent();
}
void TabRowControl::WorkspaceName(const winrt::hstring& value)
{
if (_WorkspaceName != value)
{
_WorkspaceName = value;
PropertyChanged.raise(*this, WUX::Data::PropertyChangedEventArgs{ L"WorkspaceName" });
}
}
// Method Description:
// - Bound in the Xaml editor to the [+] button.
// Arguments:

View File

@@ -19,14 +19,6 @@ namespace winrt::TerminalApp::implementation
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(bool, ShowElevationShield, PropertyChanged.raise, false);
WINRT_OBSERVABLE_PROPERTY(bool, ShowWindowsButton, PropertyChanged.raise, true);
public:
winrt::hstring WorkspaceName() const noexcept { return _WorkspaceName; }
void WorkspaceName(const winrt::hstring& value);
private:
winrt::hstring _WorkspaceName{};
};
}

View File

@@ -9,7 +9,5 @@ namespace TerminalApp
TabRowControl();
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
Boolean ShowElevationShield;
Boolean ShowWindowsButton;
String WorkspaceName;
}
}

View File

@@ -8,7 +8,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
Background="{ThemeResource TabViewBackground}"
mc:Ignorable="d">
@@ -36,44 +35,14 @@
TabWidthMode="Equal">
<mux:TabView.TabStripHeader>
<StackPanel Orientation="Horizontal">
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
<!-- Workspace/windows button -->
<Button x:Name="WorkspaceDropdown"
Margin="4,0,0,4"
Padding="8,0,0,0"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Visibility="{x:Bind ShowWindowsButton, Mode=OneWay}">
<Button.Content>
<StackPanel Orientation="Horizontal"
Spacing="8">
<!-- EE40 is the "TaskViewSettings" glyph -->
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xEE40;" />
<TextBlock x:Name="WorkspaceNameText"
Padding="0,0,8,0"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind WorkspaceName, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(WorkspaceName), Mode=OneWay}" />
</StackPanel>
</Button.Content>
<Button.Flyout>
<MenuFlyout x:Name="WorkspaceFlyout" />
</Button.Flyout>
</Button>
</StackPanel>
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
</mux:TabView.TabStripHeader>
<mux:TabView.TabStripFooter>

View File

@@ -25,8 +25,6 @@
#include "TerminalSettingsCache.h"
#include "LaunchPositionRequest.g.cpp"
#include "WindowListEntry.g.cpp"
#include "WindowListRequest.g.cpp"
#include "RenameWindowRequestedArgs.g.cpp"
#include "RequestMoveContentArgs.g.cpp"
#include "TerminalPage.g.cpp"
@@ -336,21 +334,6 @@ namespace winrt::TerminalApp::implementation
auto tabRowImpl = winrt::get_self<implementation::TabRowControl>(_tabRow);
_newTabButton = tabRowImpl->NewTabButton();
_workspaceFlyout = tabRowImpl->WorkspaceFlyout();
_workspaceDropdown = tabRowImpl->WorkspaceDropdown();
// Set the initial workspace name from the window name.
// Use raw WindowName() so unnamed windows show no text.
_tabRow.WorkspaceName(_WindowProperties.WindowName());
// Rebuild the workspace flyout each time it opens so it always
// reflects the latest set of persisted workspaces.
_workspaceFlyout.Opening([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_PopulateWorkspaceFlyout();
}
});
if (_settings.GlobalSettings().ShowTabsInTitlebar())
{
@@ -460,12 +443,6 @@ namespace winrt::TerminalApp::implementation
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
// Apply the ShowWindowsButton theme setting.
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
{
_tabRow.ShowWindowsButton(theme.Window() ? theme.Window().ShowWindowsButton() : true);
}
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
DispatcherQueue::GetForCurrentThread(),
til::throttled_func_options{
@@ -2308,14 +2285,14 @@ namespace winrt::TerminalApp::implementation
QuitRequested.raise(nullptr, nullptr);
}
WindowLayout TerminalPage::GetWindowLayout()
void TerminalPage::PersistState()
{
// This method may be called for a window even if it hasn't had a tab yet or lost all of them.
// We shouldn't persist such windows.
const auto tabCount = _tabs.Size();
if (_startupState != StartupState::Initialized || tabCount == 0)
{
return nullptr;
return;
}
std::vector<ActionAndArgs> actions;
@@ -2330,7 +2307,7 @@ namespace winrt::TerminalApp::implementation
// Avoid persisting a window with zero tabs, because `BuildStartupActions` happened to return an empty vector.
if (actions.empty())
{
return nullptr;
return;
}
// if the focused tab was not the last tab, restore that
@@ -2379,49 +2356,7 @@ namespace winrt::TerminalApp::implementation
RequestLaunchPosition.raise(*this, launchPosRequest);
layout.InitialPosition(launchPosRequest.Position());
return layout;
}
void TerminalPage::PersistState()
{
// There are two persistence mechanisms in play here:
// * PersistedWindowLayouts (vector) — consumed on next startup to
// re-open a matching set of windows. Cleared after restore.
// * PersistedWorkspaces (name-keyed map) — the full tab/buffer
// state of a named window, claimed by name on demand via
// ApplicationState::TakeWorkspace.
//
// For named windows we save the full layout into the workspace map
// and drop a lightweight `openWorkspace` stub into the generic vector,
// so the generic restore path re-opens the named window which in
// turn claims its own workspace. Unnamed windows don't have a stable
// key, so their full layout is stored directly in the vector.
if (const auto layout = GetWindowLayout())
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
// Persist the full layout into the workspace collection.
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
// Build a minimal layout with just an openWorkspace action
// so the generic restore path re-opens this workspace by name.
std::vector<ActionAndArgs> actions;
ActionAndArgs action;
action.Action(ShortcutAction::OpenWorkspace);
OpenWorkspaceArgs args{ windowName };
action.Args(args);
actions.emplace_back(std::move(action));
WindowLayout stub;
stub.TabLayout(winrt::single_threaded_vector<ActionAndArgs>(std::move(actions)));
ApplicationState::SharedInstance().AppendPersistedWindowLayout(stub);
}
else
{
ApplicationState::SharedInstance().AppendPersistedWindowLayout(layout);
}
}
ApplicationState::SharedInstance().AppendPersistedWindowLayout(layout);
}
// Method Description:
@@ -4122,12 +4057,6 @@ namespace winrt::TerminalApp::implementation
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
// Apply the ShowWindowsButton theme setting.
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
{
_tabRow.ShowWindowsButton(theme.Window() ? theme.Window().ShowWindowsButton() : true);
}
Media::SolidColorBrush transparent{ Windows::UI::Colors::Transparent() };
_tabView.Background(transparent);
@@ -5768,199 +5697,6 @@ namespace winrt::TerminalApp::implementation
}
}
// Rebuild the workspace flyout contents. Called every time the flyout opens
// Rebuild the workspace flyout contents. Called every time the flyout opens
// so it reflects the current set of persisted workspaces.
void TerminalPage::_PopulateWorkspaceFlyout()
{
if (!_workspaceFlyout)
{
return;
}
_workspaceFlyout.Items().Clear();
// --- "Name / Rename this window" ---
{
MenuFlyoutItem item{};
item.Text(_WindowProperties.WindowName().empty() ? RS_(L"NameThisWindowMenuItem") : RS_(L"RenameThisWindowMenuItem"));
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE8AC"); // Rename glyph
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
item.Icon(iconElement);
item.Click([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_actionDispatch->DoAction(ActionAndArgs{ ShortcutAction::OpenWindowRenamer, nullptr });
}
});
_workspaceFlyout.Items().Append(item);
}
// --- Gather open window info first so we can filter workspaces ---
const auto windowListReq{ winrt::make<WindowListRequest>() };
RequestWindowList.raise(*this, windowListReq);
const auto windowEntries = windowListReq.Entries();
std::set<winrt::hstring> openWindowNames;
if (windowEntries)
{
for (const auto& entry : windowEntries)
{
const auto& name = entry.Name();
if (!name.empty())
{
openWindowNames.emplace(name);
}
}
}
// --- Saved workspaces section (only those not currently open) ---
// Collect workspace names that aren't currently open so we can show
// them both as top-level "open" items and inside the delete sub-menu.
const auto workspaces = ApplicationState::SharedInstance().AllPersistedWorkspaces();
if (workspaces && workspaces.Size() > 0)
{
bool addedSeparator = false;
for (const auto& pair : workspaces)
{
const auto name = pair.Key();
// Skip workspaces that correspond to a currently-open window.
if (openWindowNames.count(name))
{
continue;
}
if (!addedSeparator)
{
_workspaceFlyout.Items().Append(MenuFlyoutSeparator{});
addedSeparator = true;
}
MenuFlyoutItem item{};
item.Text(name);
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE8F1"); // SwitchApps glyph
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
item.Icon(iconElement);
item.Click([weakThis{ get_weak() }, name](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_OpenWorkspaceWindow(name);
}
});
// Right-click to delete: attach a context flyout with a
// "Delete workspace?" item that opens a confirmation dialog.
{
WUX::Controls::MenuFlyout deleteFlyout{};
deleteFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedRight);
WUX::Controls::MenuFlyoutItem deleteItem{};
deleteItem.Text(RS_(L"DeleteWorkspaceMenuItem"));
WUX::Controls::FontIcon trashIcon{};
trashIcon.Glyph(L"\xE74D"); // Delete glyph
trashIcon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
deleteItem.Icon(trashIcon);
deleteItem.Click([weakThis{ get_weak() }, name](auto&&, auto&&) -> safe_void_coroutine {
auto page{ weakThis.get() };
if (!page)
{
co_return;
}
// Build and show a confirmation ContentDialog.
ContentDialog dialog{};
dialog.Title(winrt::box_value(winrt::hstring{ RS_fmt(L"ConfirmDeleteWorkspaceTitle", name) }));
dialog.PrimaryButtonText(RS_(L"ConfirmDeleteWorkspaceDelete"));
dialog.CloseButtonText(RS_(L"ConfirmDeleteWorkspaceCancel"));
dialog.DefaultButton(ContentDialogButton::Close);
if (auto presenter{ page->_dialogPresenter.get() })
{
const auto result = co_await presenter.ShowDialog(dialog);
// Re-check after co_await
page = weakThis.get();
if (!page)
{
co_return;
}
if (result == ContentDialogResult::Primary)
{
ApplicationState::SharedInstance().RemoveWorkspace(name);
page->_PopulateWorkspaceFlyout();
}
}
});
deleteFlyout.Items().Append(deleteItem);
WUX::Controls::Primitives::FlyoutBase::SetAttachedFlyout(item, deleteFlyout);
item.ContextRequested([item](auto&&, auto&&) {
WUX::Controls::Primitives::FlyoutBase::ShowAttachedFlyout(item);
});
}
_workspaceFlyout.Items().Append(item);
}
}
// --- Open windows section ---
if (windowEntries && windowEntries.Size() > 0)
{
_workspaceFlyout.Items().Append(MenuFlyoutSeparator{});
const auto thisWindowId = _WindowProperties.WindowId();
for (const auto& entry : windowEntries)
{
const auto id = entry.Id();
const auto& name = entry.Name();
winrt::hstring displayText;
if (name.empty())
{
displayText = winrt::hstring{ RS_fmt(L"WindowListUnnamedEntry", id) };
}
else
{
displayText = winrt::hstring{ fmt::format(FMT_COMPILE(L"#{}: {}"), id, name) };
}
MenuFlyoutItem item{};
item.Text(displayText);
if (id == thisWindowId)
{
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE73E"); // CheckMark glyph
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
item.Icon(iconElement);
item.IsEnabled(false);
}
else
{
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE737"); // ChromeRestore glyph
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
item.Icon(iconElement);
item.Click([weakThis{ get_weak() }, id](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->SummonWindowByIdRequested.raise(*page, winrt::make<SummonWindowByIdRequestedArgs>(id));
}
});
}
_workspaceFlyout.Items().Append(item);
}
}
}
// Handler for our WindowProperties's PropertyChanged event. We'll use this
// to pop the "Identify Window" toast when the user renames our window.
void TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, const WUX::Data::PropertyChangedEventArgs& args)
@@ -5970,10 +5706,6 @@ namespace winrt::TerminalApp::implementation
return;
}
// Keep the workspace dropdown label in sync with the window name.
// Use raw WindowName() so clearing the name hides the text.
_tabRow.WorkspaceName(_WindowProperties.WindowName());
// DON'T display the confirmation if this is the name we were
// given on startup!
if (_startupState == StartupState::Initialized)

View File

@@ -10,11 +10,8 @@
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.g.h"
#include "SummonWindowByIdRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "LaunchPositionRequest.g.h"
#include "WindowListEntry.g.h"
#include "WindowListRequest.g.h"
#include "Toast.h"
#include "WindowsPackageManagerFactory.h"
@@ -76,15 +73,6 @@ namespace winrt::TerminalApp::implementation
_ProposedName{ name } {};
};
struct SummonWindowByIdRequestedArgs : SummonWindowByIdRequestedArgsT<SummonWindowByIdRequestedArgs>
{
WINRT_PROPERTY(uint64_t, WindowId);
public:
SummonWindowByIdRequestedArgs(uint64_t id) :
_WindowId{ id } {};
};
struct RequestMoveContentArgs : RequestMoveContentArgsT<RequestMoveContentArgs>
{
WINRT_PROPERTY(winrt::hstring, Window);
@@ -106,25 +94,6 @@ namespace winrt::TerminalApp::implementation
til::property<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> Position;
};
struct WindowListEntry : WindowListEntryT<WindowListEntry>
{
WindowListEntry() = default;
til::property<uint64_t> Id;
til::property<winrt::hstring> Name;
};
struct WindowListRequest : WindowListRequestT<WindowListRequest>
{
WindowListRequest() :
_Entries{ winrt::single_threaded_vector<winrt::TerminalApp::WindowListEntry>() } {}
winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::WindowListEntry> Entries() const { return _Entries; }
private:
winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::WindowListEntry> _Entries;
};
struct WinGetSearchParams
{
winrt::Microsoft::Management::Deployment::PackageMatchField Field;
@@ -163,7 +132,6 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine RequestQuit();
safe_void_coroutine CloseWindow();
winrt::Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
void PersistState();
std::vector<IPaneContent> Panes() const;
@@ -235,7 +203,6 @@ namespace winrt::TerminalApp::implementation
til::typed_event<IInspectable, IInspectable> IdentifyWindowsRequested;
til::typed_event<IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs> RenameWindowRequested;
til::typed_event<IInspectable, IInspectable> SummonWindowRequested;
til::typed_event<IInspectable, winrt::TerminalApp::SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
til::typed_event<IInspectable, winrt::TerminalApp::Tab> FocusTabRequested;
til::typed_event<IInspectable, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs> WindowSizeChanged;
@@ -248,7 +215,6 @@ namespace winrt::TerminalApp::implementation
til::typed_event<Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs> RequestReceiveContent;
til::typed_event<IInspectable, winrt::TerminalApp::LaunchPositionRequest> RequestLaunchPosition;
til::typed_event<IInspectable, winrt::TerminalApp::WindowListRequest> RequestWindowList;
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
@@ -271,8 +237,6 @@ namespace winrt::TerminalApp::implementation
TerminalApp::TabRowControl _tabRow{ nullptr };
Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr };
Microsoft::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr };
Windows::UI::Xaml::Controls::MenuFlyout _workspaceFlyout{ nullptr };
Windows::UI::Xaml::Controls::Button _workspaceDropdown{ nullptr };
winrt::TerminalApp::ColorPickupFlyout _tabColorPicker{ nullptr };
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
@@ -371,7 +335,6 @@ namespace winrt::TerminalApp::implementation
void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&);
safe_void_coroutine _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs);
safe_void_coroutine _OpenWorkspaceWindow(const winrt::hstring name);
void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@@ -614,7 +577,6 @@ namespace winrt::TerminalApp::implementation
void _PopulateContextMenu(const Microsoft::Terminal::Control::TermControl& control, const Microsoft::UI::Xaml::Controls::CommandBarFlyout& sender, const bool withSelection);
void _PopulateQuickFixMenu(const Microsoft::Terminal::Control::TermControl& control, const Windows::UI::Xaml::Controls::MenuFlyout& sender);
void _PopulateWorkspaceFlyout();
winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex);
winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender);
@@ -641,5 +603,4 @@ namespace winrt::TerminalApp::implementation
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalPage);
BASIC_FACTORY(WindowListEntry);
}

View File

@@ -19,10 +19,6 @@ namespace TerminalApp
{
String ProposedName { get; };
};
[default_interface] runtimeclass SummonWindowByIdRequestedArgs
{
UInt64 WindowId { get; };
};
[default_interface] runtimeclass RequestMoveContentArgs
{
String Window { get; };
@@ -54,20 +50,6 @@ namespace TerminalApp
Microsoft.Terminal.Settings.Model.LaunchPosition Position;
}
[default_interface] runtimeclass WindowListEntry
{
WindowListEntry();
UInt64 Id;
String Name;
}
// Raised by TerminalPage when it needs the list of open windows.
// The handler (AppHost) fills Entries synchronously.
[default_interface] runtimeclass WindowListRequest
{
Windows.Foundation.Collections.IVector<WindowListEntry> Entries { get; };
}
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
{
TerminalPage(WindowProperties properties, ContentManager manager);
@@ -111,7 +93,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.WindowSizeChangedEventArgs> WindowSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
@@ -122,6 +103,5 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
event Windows.Foundation.TypedEventHandler<Object, WindowListRequest> RequestWindowList;
}
}

View File

@@ -258,15 +258,6 @@ namespace winrt::TerminalApp::implementation
AppLogic::Current()->NotifyRootInitialized();
}
WindowLayout TerminalWindow::GetWindowLayout()
{
if (_root)
{
return _root->GetWindowLayout();
}
return nullptr;
}
void TerminalWindow::PersistState()
{
if (_root)
@@ -1112,11 +1103,6 @@ namespace winrt::TerminalApp::implementation
_initialContentArgs = wil::to_vector(args);
}
void TerminalWindow::SetPersistedLayout(const winrt::Microsoft::Terminal::Settings::Model::WindowLayout& layout)
{
_cachedLayout = layout;
}
// Method Description:
// - Parse the provided commandline arguments into actions, and try to
// perform them immediately.
@@ -1237,14 +1223,7 @@ namespace winrt::TerminalApp::implementation
void TerminalWindow::WindowName(const winrt::hstring& name)
{
const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow();
const auto oldName = _WindowProperties->WindowName();
_WindowProperties->WindowName(name);
// If this window had a persisted workspace under the old name, rename
// that entry too so we don't leave a stale copy behind.
if (!oldName.empty() && !name.empty() && oldName != name)
{
ApplicationState::SharedInstance().RenameWorkspace(oldName, name);
}
if (!_root)
{
return;

View File

@@ -71,7 +71,6 @@ namespace winrt::TerminalApp::implementation
void Create();
winrt::Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
void PersistState();
void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
@@ -80,7 +79,6 @@ namespace winrt::TerminalApp::implementation
int32_t SetStartupCommandline(TerminalApp::CommandlineArgs args);
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
void SetPersistedLayout(const winrt::Microsoft::Terminal::Settings::Model::WindowLayout& layout);
int32_t ExecuteCommandline(TerminalApp::CommandlineArgs args);
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
@@ -224,7 +222,6 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested);
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
FORWARDED_TYPED_EVENT(SummonWindowByIdRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::SummonWindowByIdRequestedArgs, _root, SummonWindowByIdRequested);
FORWARDED_TYPED_EVENT(FocusTabRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::Tab, _root, FocusTabRequested);
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);
@@ -234,7 +231,6 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
FORWARDED_TYPED_EVENT(RequestLaunchPosition, Windows::Foundation::IInspectable, winrt::TerminalApp::LaunchPositionRequest, _root, RequestLaunchPosition);
FORWARDED_TYPED_EVENT(RequestWindowList, Windows::Foundation::IInspectable, winrt::TerminalApp::WindowListRequest, _root, RequestWindowList);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@@ -56,13 +56,11 @@ namespace TerminalApp
Int32 SetStartupCommandline(CommandlineArgs args);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
void SetPersistedLayout(Microsoft.Terminal.Settings.Model.WindowLayout layout);
Int32 ExecuteCommandline(CommandlineArgs args);
Boolean ShouldImmediatelyHandoffToElevated();
void HandoffToElevated();
Microsoft.Terminal.Settings.Model.WindowLayout GetWindowLayout();
void PersistState();
Windows.UI.Xaml.UIElement GetRoot();
@@ -130,7 +128,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.Tab> FocusTabRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
@@ -143,7 +140,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
event Windows.Foundation.TypedEventHandler<Object, WindowListRequest> RequestWindowList;
void AttachContent(String content, UInt32 tabIndex);
void SendContentToOther(RequestReceiveContentArgs args);

View File

@@ -81,7 +81,7 @@
CurrentValueAccessibleName="{x:Bind Appearance.CurrentColorScheme.Name, Mode=OneWay}"
HasSettingValue="{x:Bind Appearance.HasDarkColorSchemeName, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.DarkColorSchemeNameOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:SettingContainer.CurrentValue>
<ComboBox Padding="4"
ItemsSource="{x:Bind Appearance.SchemesList, Mode=OneWay}"
@@ -220,7 +220,7 @@
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Appearance.HasForeground, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.ForegroundOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:NullableColorPicker x:Uid="Profile_Foreground_NullableColorPicker"
ColorSchemeVM="{x:Bind Appearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Appearance.Foreground, Mode=TwoWay}"
@@ -236,7 +236,7 @@
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Appearance.HasBackground, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.BackgroundOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:NullableColorPicker x:Uid="Profile_Background_NullableColorPicker"
ColorSchemeVM="{x:Bind Appearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Appearance.Background, Mode=TwoWay}"
@@ -252,7 +252,7 @@
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Appearance.HasSelectionBackground, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.SelectionBackgroundOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:NullableColorPicker x:Uid="Profile_SelectionBackground_NullableColorPicker"
ColorSchemeVM="{x:Bind Appearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Appearance.SelectionBackground, Mode=TwoWay}"
@@ -536,7 +536,7 @@
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Appearance.HasCursorColor, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.CursorColorOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:NullableColorPicker x:Uid="Profile_CursorColor_NullableColorPicker"
ColorSchemeVM="{x:Bind Appearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Appearance.CursorColor, Mode=TwoWay}"

View File

@@ -8,8 +8,9 @@
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
<!-- Merge SettingContainerStyle here to give every page access to the SettingContainer -->
<!-- Merge SettingsControls and SettingContainer styles here to give every page access to them -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="SettingsControlsStyle.xaml" />
<ResourceDictionary Source="SettingContainerStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
@@ -1200,87 +1201,6 @@
</Setter>
</Style>
<Style x:Key="NavigatorButtonStyle"
TargetType="Button">
<Setter Property="Background" Value="{ThemeResource ExpanderHeaderBackground}" />
<Setter Property="MinWidth" Value="{ThemeResource FlyoutThemeMinWidth}" />
<Setter Property="MinHeight" Value="64" />
<Setter Property="BorderThickness" Value="{ThemeResource ExpanderHeaderBorderThickness}" />
<Setter Property="BorderBrush" Value="{ThemeResource ExpanderHeaderBorderBrush}" />
<Setter Property="Padding" Value="16,0,8,0" />
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="Grid"
Padding="{TemplateBinding Padding}"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="ContentPresenter"
Grid.Column="0"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
<FontIcon Grid.Column="1"
Margin="20,0,8,0"
HorizontalAlignment="Right"
FontSize="10"
FontWeight="Black"
Glyph="&#xE76C;" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ToggleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ToggleButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ToggleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ToggleButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="NewInfoBadge"
TargetType="muxc:InfoBadge">
<Setter Property="Padding" Value="5,1,5,2" />

View File

@@ -0,0 +1,139 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ControlSizeTrigger.h"
#include "ControlSizeTrigger.g.cpp"
#include <limits>
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DependencyProperty ControlSizeTrigger::_CanTriggerProperty{ nullptr };
DependencyProperty ControlSizeTrigger::_MinWidthProperty{ nullptr };
DependencyProperty ControlSizeTrigger::_MaxWidthProperty{ nullptr };
DependencyProperty ControlSizeTrigger::_MinHeightProperty{ nullptr };
DependencyProperty ControlSizeTrigger::_MaxHeightProperty{ nullptr };
DependencyProperty ControlSizeTrigger::_TargetElementProperty{ nullptr };
ControlSizeTrigger::ControlSizeTrigger()
{
_InitializeProperties();
}
void ControlSizeTrigger::_InitializeProperties()
{
// Defaults mirror the toolkit: trigger is always evaluatable, bounds
// are wide open, no target element until one is bound.
if (!_CanTriggerProperty)
{
_CanTriggerProperty = DependencyProperty::Register(
L"CanTrigger",
xaml_typename<bool>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ box_value(true), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
}
if (!_MinWidthProperty)
{
_MinWidthProperty = DependencyProperty::Register(
L"MinWidth",
xaml_typename<double>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ box_value(0.0), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
}
if (!_MaxWidthProperty)
{
_MaxWidthProperty = DependencyProperty::Register(
L"MaxWidth",
xaml_typename<double>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ box_value(std::numeric_limits<double>::infinity()), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
}
if (!_MinHeightProperty)
{
_MinHeightProperty = DependencyProperty::Register(
L"MinHeight",
xaml_typename<double>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ box_value(0.0), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
}
if (!_MaxHeightProperty)
{
_MaxHeightProperty = DependencyProperty::Register(
L"MaxHeight",
xaml_typename<double>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ box_value(std::numeric_limits<double>::infinity()), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
}
if (!_TargetElementProperty)
{
_TargetElementProperty = DependencyProperty::Register(
L"TargetElement",
xaml_typename<FrameworkElement>(),
xaml_typename<Editor::ControlSizeTrigger>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &ControlSizeTrigger::_OnTargetElementChanged } });
}
}
void ControlSizeTrigger::_OnTriggerInputChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
if (const auto obj{ d.try_as<Editor::ControlSizeTrigger>() })
{
get_self<ControlSizeTrigger>(obj)->_UpdateTrigger();
}
}
void ControlSizeTrigger::_OnTargetElementChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& e)
{
const auto obj{ d.try_as<Editor::ControlSizeTrigger>() };
if (!obj)
{
return;
}
const auto oldElement = e.OldValue().try_as<FrameworkElement>();
const auto newElement = e.NewValue().try_as<FrameworkElement>();
get_self<ControlSizeTrigger>(obj)->_UpdateTargetElement(oldElement, newElement);
}
void ControlSizeTrigger::_UpdateTargetElement(const FrameworkElement& /*oldValue*/, const FrameworkElement& newValue)
{
// Revoking handles both unhooking the previous element and a null `newValue`.
_sizeChangedRevoker.revoke();
if (newValue)
{
_sizeChangedRevoker = newValue.SizeChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_UpdateTrigger();
}
});
}
_UpdateTrigger();
}
void ControlSizeTrigger::_UpdateTrigger()
{
const auto target = TargetElement();
if (!target || !CanTrigger())
{
_isActive = false;
SetActive(false);
return;
}
const auto width = target.ActualWidth();
const auto height = target.ActualHeight();
const bool activate =
MinWidth() <= width &&
width < MaxWidth() &&
MinHeight() <= height &&
height < MaxHeight();
_isActive = activate;
SetActive(activate);
}
}

View File

@@ -0,0 +1,64 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- ControlSizeTrigger
Abstract:
- A conditional state trigger that activates based on the size (width and/or
height) of a target FrameworkElement. Lets XAML visual states swap based on
the live size of a templated part. Ported from the Windows Community Toolkit
primitive `CommunityToolkit.WinUI.ControlSizeTrigger`.
The trigger is "active" when:
MinWidth <= TargetElement.ActualWidth < MaxWidth AND
MinHeight <= TargetElement.ActualHeight < MaxHeight
Defaults: MinWidth = MinHeight = 0; MaxWidth = MaxHeight = +inf, which makes
the trigger always active unless `CanTrigger` is false or `TargetElement` is
null.
Author(s):
- Carlos Zamora - May 2026 (port from CommunityToolkit.WinUI.ControlSizeTrigger)
--*/
#pragma once
#include "ControlSizeTrigger.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct ControlSizeTrigger : ControlSizeTriggerT<ControlSizeTrigger>
{
public:
ControlSizeTrigger();
bool IsActive() const { return _isActive; }
DEPENDENCY_PROPERTY(bool, CanTrigger);
DEPENDENCY_PROPERTY(double, MinWidth);
DEPENDENCY_PROPERTY(double, MaxWidth);
DEPENDENCY_PROPERTY(double, MinHeight);
DEPENDENCY_PROPERTY(double, MaxHeight);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::FrameworkElement, TargetElement);
private:
static void _InitializeProperties();
static void _OnTriggerInputChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnTargetElementChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
void _UpdateTargetElement(const Windows::UI::Xaml::FrameworkElement& oldValue, const Windows::UI::Xaml::FrameworkElement& newValue);
void _UpdateTrigger();
Windows::UI::Xaml::FrameworkElement::SizeChanged_revoker _sizeChangedRevoker;
bool _isActive{ false };
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(ControlSizeTrigger);
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass ControlSizeTrigger : Windows.UI.Xaml.StateTriggerBase
{
ControlSizeTrigger();
Boolean CanTrigger;
static Windows.UI.Xaml.DependencyProperty CanTriggerProperty { get; };
Double MinWidth;
static Windows.UI.Xaml.DependencyProperty MinWidthProperty { get; };
Double MaxWidth;
static Windows.UI.Xaml.DependencyProperty MaxWidthProperty { get; };
Double MinHeight;
static Windows.UI.Xaml.DependencyProperty MinHeightProperty { get; };
Double MaxHeight;
static Windows.UI.Xaml.DependencyProperty MaxHeightProperty { get; };
Windows.UI.Xaml.FrameworkElement TargetElement;
static Windows.UI.Xaml.DependencyProperty TargetElementProperty { get; };
Boolean IsActive { get; };
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "CornerRadiusFilterConverters.h"
#include "CornerRadiusConverter.g.cpp"
#include "TopCornerRadiusFilterConverter.g.cpp"
#include "BottomCornerRadiusFilterConverter.g.cpp"
using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
winrt::Windows::Foundation::IInspectable CornerRadiusConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
if (!value)
{
return value;
}
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
return box_value(CornerRadius{ 0, 0, cr.BottomRight, cr.BottomLeft });
}
winrt::Windows::Foundation::IInspectable CornerRadiusConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
return value;
}
winrt::Windows::Foundation::IInspectable TopCornerRadiusFilterConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
if (!value)
{
return value;
}
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
return box_value(CornerRadius{ cr.TopLeft, cr.TopRight, 0, 0 });
}
winrt::Windows::Foundation::IInspectable TopCornerRadiusFilterConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
return value;
}
winrt::Windows::Foundation::IInspectable BottomCornerRadiusFilterConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
if (!value)
{
return value;
}
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
return box_value(CornerRadius{ 0, 0, cr.BottomRight, cr.BottomLeft });
}
winrt::Windows::Foundation::IInspectable BottomCornerRadiusFilterConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
{
return value;
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
#pragma once
#include "CornerRadiusConverter.g.h"
#include "TopCornerRadiusFilterConverter.g.h"
#include "BottomCornerRadiusFilterConverter.g.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct CornerRadiusConverter : CornerRadiusConverterT<CornerRadiusConverter>
{
CornerRadiusConverter() = default;
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
};
struct TopCornerRadiusFilterConverter : TopCornerRadiusFilterConverterT<TopCornerRadiusFilterConverter>
{
TopCornerRadiusFilterConverter() = default;
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
};
struct BottomCornerRadiusFilterConverter : BottomCornerRadiusFilterConverterT<BottomCornerRadiusFilterConverter>
{
BottomCornerRadiusFilterConverter() = default;
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(CornerRadiusConverter);
BASIC_FACTORY(TopCornerRadiusFilterConverter);
BASIC_FACTORY(BottomCornerRadiusFilterConverter);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass CornerRadiusConverter : Windows.UI.Xaml.Data.IValueConverter
{
CornerRadiusConverter();
}
[default_interface] runtimeclass TopCornerRadiusFilterConverter : Windows.UI.Xaml.Data.IValueConverter
{
TopCornerRadiusFilterConverter();
}
[default_interface] runtimeclass BottomCornerRadiusFilterConverter : Windows.UI.Xaml.Data.IValueConverter
{
BottomCornerRadiusFilterConverter();
}
}

View File

@@ -79,7 +79,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
const auto extPkgVM = sender.as<Controls::Button>().Tag().as<Editor::ExtensionPackageViewModel>();
const auto extPkgVM = sender.as<FrameworkElement>().Tag().as<Editor::ExtensionPackageViewModel>();
_ViewModel.CurrentExtensionPackage(extPkgVM);
}

View File

@@ -127,78 +127,58 @@
<DataTemplate x:Key="DefaultExtensionNavigatorTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Style="{StaticResource NavigatorButtonStyle}"
Tag="{x:Bind}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{x:Bind}"
ContentTemplate="{StaticResource DefaultExtensionIdentifierTemplate}" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</Grid>
</Button>
<local:SettingsCard AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Header="{x:Bind Package.Source}"
IsClickEnabled="True"
Tag="{x:Bind}">
<local:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE74C;" />
</local:SettingsCard.HeaderIcon>
<ToggleSwitch HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingsCard>
</DataTemplate>
<DataTemplate x:Key="ComplexExtensionNavigatorTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Style="{StaticResource NavigatorButtonStyle}"
Tag="{x:Bind}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{x:Bind}"
ContentTemplate="{StaticResource ComplexExtensionIdentifierTemplate}" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</Grid>
</Button>
<local:SettingsCard AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Description="{x:Bind Package.Source}"
Header="{x:Bind Package.DisplayName}"
IsClickEnabled="True"
Tag="{x:Bind}">
<local:SettingsCard.HeaderIcon>
<IconSourceElement IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Package.Icon)}" />
</local:SettingsCard.HeaderIcon>
<ToggleSwitch HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingsCard>
</DataTemplate>
<DataTemplate x:Key="ComplexExtensionNavigatorTemplateWithFontIcon"
x:DataType="local:ExtensionPackageViewModel">
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Style="{StaticResource NavigatorButtonStyle}"
Tag="{x:Bind}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{x:Bind}"
ContentTemplate="{StaticResource ComplexExtensionIdentifierTemplateWithFontIcon}" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</Grid>
</Button>
<local:SettingsCard AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Description="{x:Bind Package.Source}"
Header="{x:Bind Package.DisplayName}"
IsClickEnabled="True"
Tag="{x:Bind}">
<local:SettingsCard.HeaderIcon>
<FontIcon Glyph="{x:Bind Package.Icon}" />
</local:SettingsCard.HeaderIcon>
<ToggleSwitch HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingsCard>
</DataTemplate>
<DataTemplate x:Key="FragmentProfileViewModelTemplate"
@@ -502,9 +482,11 @@
<!-- Scope -->
<local:SettingContainer x:Name="Scope"
x:Uid="Extensions_Scope"
Content="{x:Bind ViewModel.CurrentExtensionPackage.Scope, Mode=OneWay}"
IsTabStop="False"
Style="{StaticResource SettingContainerWithTextContent}" />
IsTabStop="False">
<TextBlock VerticalAlignment="Center"
Style="{ThemeResource SecondaryTextBlockStyle}"
Text="{x:Bind ViewModel.CurrentExtensionPackage.Scope, Mode=OneWay}" />
</local:SettingContainer>
<!-- JSON -->
<ItemsControl IsTabStop="False"
ItemTemplate="{StaticResource JsonTemplate}"

View File

@@ -62,9 +62,9 @@
<!-- Always show tabs -->
<local:SettingContainer x:Name="AlwaysShowTabs"
x:Uid="Globals_AlwaysShowTabs">
<ToggleSwitch IsEnabled="{x:Bind mtu:Converters.InvertBoolean(ViewModel.ShowTabsInTitlebar), Mode=OneWay}"
IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}"
x:Uid="Globals_AlwaysShowTabs"
IsEnabled="{x:Bind mtu:Converters.InvertBoolean(ViewModel.ShowTabsInTitlebar), Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>

View File

@@ -173,6 +173,21 @@
<ClInclude Include="SettingContainer.h">
<DependentUpon>SettingContainer.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SettingsCard.h">
<DependentUpon>SettingsCard.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SettingsExpander.h">
<DependentUpon>SettingsExpander.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ControlSizeTrigger.h">
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
</ClInclude>
<ClInclude Include="CornerRadiusFilterConverters.h">
<DependentUpon>CornerRadiusFilterConverters.idl</DependentUpon>
</ClInclude>
<ClInclude Include="StringDefaultTemplateSelector.h">
<DependentUpon>StringDefaultTemplateSelector.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Utils.h" />
<ClInclude Include="PreviewConnection.h" />
<ClInclude Include="$(GeneratedFilesDir)GeneratedSettingsIndex.g.h" />
@@ -252,6 +267,9 @@
<Page Include="SettingContainerStyle.xaml">
<Type>DefaultStyle</Type>
</Page>
<Page Include="SettingsControlsStyle.xaml">
<Type>DefaultStyle</Type>
</Page>
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
@@ -385,6 +403,21 @@
<ClCompile Include="SettingContainer.cpp">
<DependentUpon>SettingContainer.idl</DependentUpon>
</ClCompile>
<ClCompile Include="SettingsCard.cpp">
<DependentUpon>SettingsCard.idl</DependentUpon>
</ClCompile>
<ClCompile Include="SettingsExpander.cpp">
<DependentUpon>SettingsExpander.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ControlSizeTrigger.cpp">
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
</ClCompile>
<ClCompile Include="CornerRadiusFilterConverters.cpp">
<DependentUpon>CornerRadiusFilterConverters.idl</DependentUpon>
</ClCompile>
<ClCompile Include="StringDefaultTemplateSelector.cpp">
<DependentUpon>StringDefaultTemplateSelector.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Utils.cpp" />
<ClCompile Include="PreviewConnection.cpp">
<DependentUpon>PreviewConnection.h</DependentUpon>
@@ -492,6 +525,21 @@
<Midl Include="SettingContainer.idl">
<SubType>Code</SubType>
</Midl>
<Midl Include="SettingsCard.idl">
<SubType>Code</SubType>
</Midl>
<Midl Include="SettingsExpander.idl">
<SubType>Code</SubType>
</Midl>
<Midl Include="ControlSizeTrigger.idl">
<SubType>Code</SubType>
</Midl>
<Midl Include="CornerRadiusFilterConverters.idl">
<SubType>Code</SubType>
</Midl>
<Midl Include="StringDefaultTemplateSelector.idl">
<SubType>Code</SubType>
</Midl>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>

View File

@@ -30,6 +30,11 @@
<Midl Include="LaunchViewModel.idl" />
<Midl Include="EnumEntry.idl" />
<Midl Include="SettingContainer.idl" />
<Midl Include="SettingsCard.idl" />
<Midl Include="SettingsExpander.idl" />
<Midl Include="ControlSizeTrigger.idl" />
<Midl Include="CornerRadiusFilterConverters.idl" />
<Midl Include="StringDefaultTemplateSelector.idl" />
<Midl Include="TerminalColorConverters.idl" />
<Midl Include="NewTabMenuViewModel.idl" />
</ItemGroup>
@@ -52,6 +57,7 @@
<Page Include="Actions.xaml" />
<Page Include="EditAction.xaml" />
<Page Include="SettingContainerStyle.xaml" />
<Page Include="SettingsControlsStyle.xaml" />
<Page Include="AddProfile.xaml" />
<Page Include="KeyChordListener.xaml" />
<Page Include="NullableColorPicker.xaml" />

View File

@@ -332,7 +332,7 @@
<local:SettingContainer x:Name="CurrentFolderIcon"
x:Uid="NewTabMenu_CurrentFolderIcon"
CurrentValueAccessibleName="{x:Bind ViewModel.CurrentFolderLocalizedIcon, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:SettingContainer.CurrentValue>
<Grid>
<ContentControl Width="16"
@@ -378,8 +378,7 @@
<!-- Add Profile -->
<local:SettingContainer x:Name="AddProfile"
x:Uid="NewTabMenu_AddProfile"
FontIconGlyph="&#xE756;"
Style="{StaticResource SettingContainerWithIcon}">
FontIconGlyph="&#xE756;">
<StackPanel Orientation="Horizontal"
Spacing="4">
@@ -428,8 +427,7 @@
<!-- Add Separator -->
<local:SettingContainer x:Name="AddSeparator"
x:Uid="NewTabMenu_AddSeparator"
FontIconGlyph="&#xE76f;"
Style="{StaticResource SettingContainerWithIcon}">
FontIconGlyph="&#xE76f;">
<Button x:Name="AddSeparatorButton"
x:Uid="NewTabMenu_AddSeparatorButton"
HorizontalAlignment="Stretch"
@@ -445,8 +443,7 @@
<!-- Add Folder -->
<local:SettingContainer x:Name="AddFolder"
x:Uid="NewTabMenu_AddFolder"
FontIconGlyph="&#xF12B;"
Style="{StaticResource SettingContainerWithIcon}">
FontIconGlyph="&#xF12B;">
<StackPanel Orientation="Horizontal"
Spacing="5">
<TextBox x:Name="FolderNameTextBox"
@@ -473,7 +470,7 @@
<local:SettingContainer x:Name="AddMatchProfiles"
x:Uid="NewTabMenu_AddMatchProfiles"
FontIconGlyph="&#xE748;"
Style="{StaticResource ExpanderSettingContainerStyleWithIcon}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel Spacing="8">
<HyperlinkButton x:Uid="NewTabMenu_AddMatchProfiles_Help"
NavigateUri="https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference" />
@@ -500,8 +497,7 @@
<!-- Add Remaining Profiles -->
<local:SettingContainer x:Name="AddRemainingProfiles"
x:Uid="NewTabMenu_AddRemainingProfiles"
FontIconGlyph="&#xE902;"
Style="{StaticResource SettingContainerWithIcon}">
FontIconGlyph="&#xE902;">
<Button x:Name="AddRemainingProfilesButton"
x:Uid="NewTabMenu_AddRemainingProfilesButton"
HorizontalAlignment="Stretch"

View File

@@ -22,9 +22,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Automation::AutomationProperties::SetFullDescription(StartingDirectoryUseParentCheckbox(), unbox_value<hstring>(startingDirCheckboxTooltip));
Automation::AutomationProperties::SetName(DeleteButton(), RS_(L"Profile_DeleteButton/Text"));
AppearanceNavigator().Content(box_value(RS_(L"Profile_Appearance/Header")));
TerminalNavigator().Content(box_value(RS_(L"Profile_Terminal/Header")));
AdvancedNavigator().Content(box_value(RS_(L"Profile_Advanced/Header")));
AppearanceNavigator().Header(box_value(RS_(L"Profile_Appearance/Header")));
TerminalNavigator().Header(box_value(RS_(L"Profile_Terminal/Header")));
AdvancedNavigator().Header(box_value(RS_(L"Profile_Advanced/Header")));
}
void Profiles_Base::OnNavigatedTo(const NavigationEventArgs& e)

View File

@@ -106,7 +106,7 @@
CurrentValueAccessibleName="{x:Bind Profile.LocalizedIcon, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasIcon, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.IconOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:SettingContainer.CurrentValue>
<Grid>
<ContentControl Width="16"
@@ -150,7 +150,7 @@
CurrentValueTemplate="{StaticResource ColorPreviewTemplate}"
HasSettingValue="{x:Bind Profile.HasTabColor, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.TabColorOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
Style="{StaticResource ExpanderSettingContainerStyle}">
<local:NullableColorPicker x:Uid="Profile_TabColor_NullableColorPicker"
ColorSchemeVM="{x:Bind Profile.DefaultAppearance.CurrentColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind Profile.TabColor, Mode=TwoWay}"
@@ -179,15 +179,15 @@
Margin="0,32,0,4"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<Button x:Name="AppearanceNavigator"
Click="Appearance_Click"
Style="{StaticResource NavigatorButtonStyle}" />
<Button x:Name="TerminalNavigator"
Click="Terminal_Click"
Style="{StaticResource NavigatorButtonStyle}" />
<Button x:Name="AdvancedNavigator"
Click="Advanced_Click"
Style="{StaticResource NavigatorButtonStyle}" />
<local:SettingsCard x:Name="AppearanceNavigator"
Click="Appearance_Click"
IsClickEnabled="True" />
<local:SettingsCard x:Name="TerminalNavigator"
Click="Terminal_Click"
IsClickEnabled="True" />
<local:SettingsCard x:Name="AdvancedNavigator"
Click="Advanced_Click"
IsClickEnabled="True" />
<!-- Delete Button -->
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<Button x:Name="DeleteButton"

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "SettingContainer.h"
#include "SettingsExpander.h"
#include "SettingContainer.g.cpp"
using namespace winrt::Windows::UI::Xaml;
@@ -54,7 +55,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
L"FontIconGlyph",
xaml_typename<hstring>(),
xaml_typename<Editor::SettingContainer>(),
PropertyMetadata{ box_value(L"") });
PropertyMetadata{ box_value(L""), PropertyChangedCallback{ &SettingContainer::_OnFontIconGlyphChanged } });
}
if (!_CurrentValueProperty)
{
@@ -134,13 +135,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void SettingContainer::_UpdateHelpText()
{
const auto helpText{ HelpText() };
// Forward HelpText into the SettingsCard / SettingsExpander Description
// slot so it renders through the WCT-ported PART_DescriptionPresenter.
// Setting it to nullptr when empty collapses the description visually so we don't
// reserve vertical space for a blank line.
const auto description{ helpText.empty() ? Windows::Foundation::IInspectable{ nullptr } : box_value(helpText) };
// Get the correct base to apply automation properties to
std::vector<DependencyObject> base;
base.reserve(2);
base.reserve(3);
if (const auto& child{ GetTemplateChild(L"Card") })
{
if (const auto& card{ child.try_as<Editor::SettingsCard>() })
{
card.Description(description);
base.push_back(child);
}
}
if (const auto& child{ GetTemplateChild(L"Expander") })
{
if (const auto& expander{ child.try_as<Microsoft::UI::Xaml::Controls::Expander>() })
if (const auto& expander{ child.try_as<Editor::SettingsExpander>() })
{
expander.Description(description);
base.push_back(child);
}
}
@@ -160,7 +178,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Automation::AutomationProperties::SetName(obj, _GenerateAccessibleName());
// apply help text as tooltip and full description (automation property)
if (const auto& helpText{ HelpText() }; !helpText.empty())
if (!helpText.empty())
{
Automation::AutomationProperties::SetFullDescription(obj, helpText);
}
@@ -170,15 +188,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Automation::AutomationProperties::SetFullDescription(obj, L"");
}
}
const auto textBlockHidden = HelpText().empty();
if (const auto& child{ GetTemplateChild(L"HelpTextBlock") })
{
if (const auto& textBlock{ child.try_as<Controls::TextBlock>() })
{
textBlock.Visibility(textBlockHidden ? Visibility::Collapsed : Visibility::Visible);
}
}
}
void SettingContainer::OnApplyTemplate()
@@ -223,13 +232,54 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_UpdateOverrideSystem();
_UpdateHelpText();
_UpdateHeaderIcon();
}
void SettingContainer::_UpdateHeaderIcon()
{
// Only forward FontIconGlyph into HeaderIcon when the caller actually
// supplied a glyph. Some templates (Warning/Error) already set
// SettingsCard.HeaderIcon in XAML to a stacked severity glyph; if we
// unconditionally wrote nullptr here we'd clobber that XAML default for
// every callsite that doesn't set FontIconGlyph.
const auto glyph{ FontIconGlyph() };
if (glyph.empty())
{
return;
}
Controls::FontIcon fi;
fi.Glyph(glyph);
const Controls::IconElement icon{ fi };
if (const auto& cardChild{ GetTemplateChild(L"Card") })
{
if (const auto& card{ cardChild.try_as<Editor::SettingsCard>() })
{
card.HeaderIcon(icon);
return;
}
}
if (const auto& expanderChild{ GetTemplateChild(L"Expander") })
{
if (const auto& expander{ expanderChild.try_as<Editor::SettingsExpander>() })
{
expander.HeaderIcon(icon);
}
}
}
void SettingContainer::_OnFontIconGlyphChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/)
{
const auto& obj{ d.try_as<Editor::SettingContainer>() };
get_self<SettingContainer>(obj)->_UpdateHeaderIcon();
}
void SettingContainer::SetExpanded(bool expanded)
{
if (const auto& child{ GetTemplateChild(L"Expander") })
{
if (const auto& expander{ child.try_as<Microsoft::UI::Xaml::Controls::Expander>() })
if (const auto& expander{ child.try_as<Editor::SettingsExpander>() })
{
expander.IsExpanded(expanded);
}
@@ -271,7 +321,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (const auto& child{ GetTemplateChild(L"Expander") })
{
if (const auto& expander{ child.try_as<Microsoft::UI::Xaml::Controls::Expander>() })
if (const auto& expander{ child.try_as<Editor::SettingsExpander>() })
{
Automation::AutomationProperties::SetName(expander, _GenerateAccessibleName());
}

View File

@@ -48,11 +48,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
static void _OnCurrentValueChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnHasSettingValueChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnHelpTextChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnFontIconGlyphChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static hstring _GenerateOverrideMessage(const IInspectable& settingOrigin);
hstring _GenerateAccessibleName();
void _UpdateOverrideSystem();
void _UpdateHelpText();
void _UpdateCurrentValueAutoProp();
void _UpdateHeaderIcon();
};
}

View File

@@ -18,8 +18,6 @@
</Style>
<SolidColorBrush x:Key="SubgroupHeaderBrush"
Color="{StaticResource TextFillColorSecondary}" />
<StaticResource x:Key="ExpanderHeaderBorderBrush"
ResourceKey="CardStrokeColorDefaultBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityBackgroundBrush"
ResourceKey="SystemFillColorCriticalBackgroundBrush" />
@@ -31,28 +29,14 @@
<StaticResource x:Key="SettingContainerWarningSeverityIconBackground"
ResourceKey="SystemFillColorCautionBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityIconForeground"
ResourceKey="TextFillColorInverseBrush" />
<StaticResource x:Key="SettingContainerWarningSeverityIconForeground"
ResourceKey="TextFillColorInverseBrush" />
<StaticResource x:Key="SettingContainerTitleForeground"
ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="SettingContainerMessageForeground"
ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="SettingContainerResetButtonIconForeground"
ResourceKey="SystemAccentColorDark2" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<Style x:Key="SecondaryTextBlockStyle"
TargetType="TextBlock" />
<!-- Do not mess with the foreground color for High Contrast. Let it ride as is. -->
<SolidColorBrush x:Key="SubgroupHeaderBrush"
Color="{ThemeResource SystemColorWindowTextColor}" />
<StaticResource x:Key="ExpanderHeaderBorderBrush"
ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityBackgroundBrush"
ResourceKey="SystemColorWindowColorBrush" />
@@ -64,16 +48,6 @@
<StaticResource x:Key="SettingContainerWarningSeverityIconBackground"
ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityIconForeground"
ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SettingContainerWarningSeverityIconForeground"
ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SettingContainerTitleForeground"
ResourceKey="SystemColorWindowTextColorBrush" />
<StaticResource x:Key="SettingContainerMessageForeground"
ResourceKey="SystemColorWindowTextColorBrush" />
<StaticResource x:Key="SettingContainerResetButtonIconForeground"
ResourceKey="SystemAccentColorLight1" />
</ResourceDictionary>
@@ -84,8 +58,6 @@
</Style>
<SolidColorBrush x:Key="SubgroupHeaderBrush"
Color="{StaticResource TextFillColorSecondary}" />
<StaticResource x:Key="ExpanderHeaderBorderBrush"
ResourceKey="CardStrokeColorDefaultBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityBackgroundBrush"
ResourceKey="SystemFillColorCriticalBackgroundBrush" />
@@ -97,36 +69,16 @@
<StaticResource x:Key="SettingContainerWarningSeverityIconBackground"
ResourceKey="SystemFillColorCautionBrush" />
<StaticResource x:Key="SettingContainerErrorSeverityIconForeground"
ResourceKey="TextFillColorInverseBrush" />
<StaticResource x:Key="SettingContainerWarningSeverityIconForeground"
ResourceKey="TextFillColorInverseBrush" />
<StaticResource x:Key="SettingContainerTitleForeground"
ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="SettingContainerMessageForeground"
ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="SettingContainerResetButtonIconForeground"
ResourceKey="SystemAccentColorLight2" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<FontWeight x:Key="SettingContainerTitleFontWeight">SemiBold</FontWeight>
<x:String x:Key="SettingContainerIconBackgroundGlyph">&#xF136;</x:String>
<x:String x:Key="SettingContainerErrorIconGlyph">&#xF13D;</x:String>
<x:String x:Key="SettingContainerWarningIconGlyph">&#xF13C;</x:String>
<Thickness x:Key="SettingContainerIconMargin">0,4,8,4</Thickness>
<x:Double x:Key="SettingContainerIconFontSize">16</x:Double>
<Style x:Key="StackPanelInExpanderStyle"
TargetType="StackPanel">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Padding" Value="0,12,0,12" />
</Style>
<Style x:Key="SettingContainerResetButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
@@ -145,18 +97,6 @@
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
</Style>
<Style x:Key="NonExpanderGrid"
TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource ExpanderHeaderBackground}" />
<Setter Property="MinWidth" Value="{ThemeResource FlyoutThemeMinWidth}" />
<Setter Property="MinHeight" Value="64" />
<Setter Property="BorderThickness" Value="{ThemeResource ExpanderHeaderBorderThickness}" />
<Setter Property="BorderBrush" Value="{ThemeResource ExpanderHeaderBorderBrush}" />
<Setter Property="Padding" Value="16,0,8,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
</Style>
<Style x:Key="SettingsPageItemHeaderStyle"
BasedOn="{StaticResource BodyTextBlockStyle}"
TargetType="TextBlock">
@@ -178,7 +118,7 @@
BasedOn="{StaticResource SettingsPageItemDescriptionStyle}"
TargetType="TextBlock">
<Setter Property="MaxWidth" Value="248" />
<Setter Property="Margin" Value="0,0,-16,0" />
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets" />
@@ -189,7 +129,9 @@
Text="{Binding}" />
</DataTemplate>
<!-- A setting container for a setting that has no additional options -->
<local:StringDefaultTemplateSelector x:Key="ExpanderPreviewTemplateSelector"
StringTemplate="{StaticResource ExpanderSettingContainerStringPreviewTemplate}" />
<Style TargetType="local:SettingContainer">
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="IsTabStop" Value="False" />
@@ -197,274 +139,64 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<local:SettingsCard x:Name="Card"
Content="{TemplateBinding Content}">
<local:SettingsCard.Header>
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<ContentControl Content="{TemplateBinding Header}"
IsTabStop="False" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<ContentPresenter Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</local:SettingsCard.Header>
</local:SettingsCard>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- A basic setting container displaying immutable text as content -->
<Style x:Key="SettingContainerWithTextContent"
TargetType="local:SettingContainer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<TextBlock Grid.Column="1"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{ThemeResource SecondaryTextBlockStyle}"
Text="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
A setting container for a setting that has no additional options.
Includes space for an icon on the left side of the header.
XAML applies the margin/padding of the icon regardless of its
existence (which caused inconsistent padding). It's easier to just create
another style and move on.
-->
<Style x:Key="SettingContainerWithIcon"
TargetType="local:SettingContainer">
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="MaxWidth" Value="1000" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="{TemplateBinding FontIconGlyph}" />
<StackPanel Grid.Column="1"
Padding="14,12,0,12"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- A setting container which can expand -->
<Style x:Key="ExpanderSettingContainerStyle"
TargetType="local:SettingContainer">
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="MaxWidth" Value="1000" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<muxc:Expander x:Name="Expander"
Margin="0,4,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Content="{TemplateBinding Content}"
IsExpanded="{TemplateBinding StartExpanded}">
<muxc:Expander.Header>
<Grid MinHeight="64">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<ContentPresenter Grid.Column="1"
Content="{TemplateBinding CurrentValue}"
ContentTemplate="{StaticResource ExpanderSettingContainerStringPreviewTemplate}" />
</Grid>
</muxc:Expander.Header>
</muxc:Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
A setting container which can expand. Includes space for an icon on the left side of the header.
XAML applies the margin/padding of the icon regardless of its
existence (which caused inconsistent padding). It's easier to just create
another style and move on.
-->
<Style x:Key="ExpanderSettingContainerStyleWithIcon"
TargetType="local:SettingContainer">
<Setter Property="MaxWidth" Value="1000" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<muxc:Expander x:Name="Expander"
Margin="0,4,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Content="{TemplateBinding Content}"
IsExpanded="{TemplateBinding StartExpanded}">
<muxc:Expander.Header>
<Grid MinHeight="64">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="{TemplateBinding FontIconGlyph}" />
<StackPanel Grid.Column="1"
Padding="14,12,0,12"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding CurrentValue}"
ContentTemplate="{StaticResource ExpanderSettingContainerStringPreviewTemplate}" />
</Grid>
</muxc:Expander.Header>
</muxc:Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- A setting container which can expand. Supports data template override for preview -->
<Style x:Key="ExpanderSettingContainerStyleWithComplexPreview"
TargetType="local:SettingContainer">
<Setter Property="MaxWidth" Value="1000" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<muxc:Expander x:Name="Expander"
Margin="0,4,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Content="{TemplateBinding Content}"
IsExpanded="{TemplateBinding StartExpanded}">
<muxc:Expander.Header>
<Grid MinHeight="64">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<ContentPresenter Grid.Column="1"
MaxWidth="248"
Margin="0,0,-16,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding CurrentValue}"
ContentTemplate="{TemplateBinding CurrentValueTemplate}" />
</Grid>
</muxc:Expander.Header>
</muxc:Expander>
<local:SettingsExpander x:Name="Expander"
IsExpanded="{TemplateBinding StartExpanded}">
<local:SettingsExpander.Header>
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<ContentControl Content="{TemplateBinding Header}"
IsTabStop="False" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
</local:SettingsExpander.Header>
<local:SettingsExpander.Content>
<ContentControl MaxWidth="248"
HorizontalAlignment="Right"
VerticalAlignment="Center"
HorizontalContentAlignment="Right"
Content="{TemplateBinding CurrentValue}"
ContentTemplate="{TemplateBinding CurrentValueTemplate}"
ContentTemplateSelector="{StaticResource ExpanderPreviewTemplateSelector}"
IsTabStop="False" />
</local:SettingsExpander.Content>
<local:SettingsExpander.ItemsHeader>
<ContentPresenter Padding="16,12,16,16"
Content="{TemplateBinding Content}" />
</local:SettingsExpander.ItemsHeader>
</local:SettingsExpander>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -479,47 +211,19 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Background="{ThemeResource SettingContainerWarningSeverityBackgroundBrush}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock x:Name="IconBackground"
Grid.Column="0"
Margin="{StaticResource SettingContainerIconMargin}"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerWarningSeverityIconBackground}"
Text="{StaticResource SettingContainerIconBackgroundGlyph}" />
<TextBlock x:Name="StandardIcon"
Grid.Column="0"
Margin="{StaticResource SettingContainerIconMargin}"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerWarningSeverityIconForeground}"
Text="{StaticResource SettingContainerWarningIconGlyph}" />
</Grid>
<TextBlock VerticalAlignment="Center"
FontWeight="{StaticResource SettingContainerTitleFontWeight}"
Foreground="{ThemeResource SettingContainerTitleForeground}"
Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Foreground="{ThemeResource SettingContainerMessageForeground}"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
</Grid>
<local:SettingsCard x:Name="Card"
Background="{ThemeResource SettingContainerWarningSeverityBackgroundBrush}">
<local:SettingsCard.Header>
<ContentControl Content="{TemplateBinding Header}"
IsTabStop="False" />
</local:SettingsCard.Header>
<local:SettingsCard.HeaderIcon>
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerWarningSeverityIconBackground}"
Glyph="{StaticResource SettingContainerWarningIconGlyph}" />
</local:SettingsCard.HeaderIcon>
</local:SettingsCard>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -534,47 +238,19 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Background="{ThemeResource SettingContainerErrorSeverityBackgroundBrush}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock x:Name="IconBackground"
Grid.Column="0"
Margin="{StaticResource SettingContainerIconMargin}"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerErrorSeverityIconBackground}"
Text="{StaticResource SettingContainerIconBackgroundGlyph}" />
<TextBlock x:Name="StandardIcon"
Grid.Column="0"
Margin="{StaticResource SettingContainerIconMargin}"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerErrorSeverityIconForeground}"
Text="{StaticResource SettingContainerErrorIconGlyph}" />
</Grid>
<TextBlock VerticalAlignment="Center"
FontWeight="{StaticResource SettingContainerTitleFontWeight}"
Foreground="{ThemeResource SettingContainerTitleForeground}"
Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Foreground="{ThemeResource SettingContainerMessageForeground}"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
</Grid>
<local:SettingsCard x:Name="Card"
Background="{ThemeResource SettingContainerErrorSeverityBackgroundBrush}">
<local:SettingsCard.Header>
<ContentControl Content="{TemplateBinding Header}"
IsTabStop="False" />
</local:SettingsCard.Header>
<local:SettingsCard.HeaderIcon>
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource SettingContainerIconFontSize}"
Foreground="{ThemeResource SettingContainerErrorSeverityIconBackground}"
Glyph="{StaticResource SettingContainerErrorIconGlyph}" />
</local:SettingsCard.HeaderIcon>
</local:SettingsCard>
</ControlTemplate>
</Setter.Value>
</Setter>

View File

@@ -0,0 +1,565 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "SettingsCard.h"
#include "SettingsCard.g.cpp"
#include "SettingsCardAutomationPeer.g.cpp"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Automation;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Controls::Primitives;
using namespace winrt::Windows::UI::Xaml::Input;
using namespace winrt::Windows::UI::Xaml::Media;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DependencyProperty SettingsCard::_HeaderProperty{ nullptr };
DependencyProperty SettingsCard::_DescriptionProperty{ nullptr };
DependencyProperty SettingsCard::_HeaderIconProperty{ nullptr };
DependencyProperty SettingsCard::_ActionIconProperty{ nullptr };
DependencyProperty SettingsCard::_ActionIconToolTipProperty{ nullptr };
DependencyProperty SettingsCard::_IsClickEnabledProperty{ nullptr };
DependencyProperty SettingsCard::_IsActionIconVisibleProperty{ nullptr };
DependencyProperty SettingsCard::_ContentAlignmentProperty{ nullptr };
static constexpr std::wstring_view NormalState{ L"Normal" };
static constexpr std::wstring_view PointerOverState{ L"PointerOver" };
static constexpr std::wstring_view PressedState{ L"Pressed" };
static constexpr std::wstring_view DisabledState{ L"Disabled" };
static constexpr std::wstring_view BitmapHeaderIconEnabledState{ L"BitmapHeaderIconEnabled" };
static constexpr std::wstring_view BitmapHeaderIconDisabledState{ L"BitmapHeaderIconDisabled" };
static constexpr std::wstring_view RightState{ L"Right" };
static constexpr std::wstring_view LeftState{ L"Left" };
static constexpr std::wstring_view VerticalState{ L"Vertical" };
static constexpr std::wstring_view NoContentSpacingState{ L"NoContentSpacing" };
static constexpr std::wstring_view ContentSpacingState{ L"ContentSpacing" };
static constexpr std::wstring_view ContentAlignmentStatesGroup{ L"ContentAlignmentStates" };
static constexpr std::wstring_view ActionIconPresenterHolder{ L"PART_ActionIconPresenterHolder" };
static constexpr std::wstring_view HeaderPresenter{ L"PART_HeaderPresenter" };
static constexpr std::wstring_view DescriptionPresenter{ L"PART_DescriptionPresenter" };
static constexpr std::wstring_view HeaderIconPresenterHolder{ L"PART_HeaderIconPresenterHolder" };
// Returns true if the given object is null, or is a string that is empty.
// Non-string non-null objects (e.g. a TextBlock) are considered "non-empty".
static bool _isNullOrEmpty(const winrt::Windows::Foundation::IInspectable& obj)
{
if (!obj)
{
return true;
}
if (const auto pv{ obj.try_as<IPropertyValue>() }; pv && pv.Type() == PropertyType::String)
{
return unbox_value_or<hstring>(obj, hstring{}).empty();
}
return false;
}
SettingsCard::SettingsCard()
{
_InitializeProperties();
}
void SettingsCard::_InitializeProperties()
{
if (!_HeaderProperty)
{
_HeaderProperty = DependencyProperty::Register(
L"Header",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsCard::_OnHeaderChanged } });
}
if (!_DescriptionProperty)
{
_DescriptionProperty = DependencyProperty::Register(
L"Description",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsCard::_OnDescriptionChanged } });
}
if (!_HeaderIconProperty)
{
_HeaderIconProperty = DependencyProperty::Register(
L"HeaderIcon",
xaml_typename<IconElement>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsCard::_OnHeaderIconChanged } });
}
if (!_ActionIconProperty)
{
_ActionIconProperty = DependencyProperty::Register(
L"ActionIcon",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ box_value(hstring{ L"\uE974" }) });
}
if (!_ActionIconToolTipProperty)
{
_ActionIconToolTipProperty = DependencyProperty::Register(
L"ActionIconToolTip",
xaml_typename<hstring>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ box_value(hstring{}) });
}
if (!_IsClickEnabledProperty)
{
_IsClickEnabledProperty = DependencyProperty::Register(
L"IsClickEnabled",
xaml_typename<bool>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ box_value(false), PropertyChangedCallback{ &SettingsCard::_OnIsClickEnabledChanged } });
}
if (!_IsActionIconVisibleProperty)
{
_IsActionIconVisibleProperty = DependencyProperty::Register(
L"IsActionIconVisible",
xaml_typename<bool>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ box_value(true), PropertyChangedCallback{ &SettingsCard::_OnIsActionIconVisibleChanged } });
}
if (!_ContentAlignmentProperty)
{
_ContentAlignmentProperty = DependencyProperty::Register(
L"ContentAlignment",
xaml_typename<Editor::SettingsCardContentAlignment>(),
xaml_typename<Editor::SettingsCard>(),
PropertyMetadata{ box_value(Editor::SettingsCardContentAlignment::Right), PropertyChangedCallback{ &SettingsCard::_OnContentAlignmentChanged } });
}
}
AutomationPeer SettingsCard::OnCreateAutomationPeer()
{
return winrt::make<implementation::SettingsCardAutomationPeer>(*this);
}
// Pointer overrides: gate the ButtonBase click pipeline on IsClickEnabled.
// When IsClickEnabled=false, we do NOT call the base method, so the event
// is left unhandled and bubbles up the visual tree. This is what lets the
// SettingsExpander header (a ToggleButton hosting a SettingsCard with
// IsClickEnabled=false) toggle on a click anywhere across the header row,
// not just on the chevron. Mirrors the Community Toolkit's SettingsCard.cs.
void SettingsCard::OnPointerPressed(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (IsClickEnabled())
{
base_type::OnPointerPressed(e);
_GoToCommonState(PressedState, true);
}
}
void SettingsCard::OnPointerReleased(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (IsClickEnabled())
{
base_type::OnPointerReleased(e);
_GoToCommonState(NormalState, true);
}
}
void SettingsCard::OnApplyTemplate()
{
// Drop any handlers from a previous template.
_isEnabledChangedRevoker.revoke();
_contentAlignmentStatesChangedRevoker.revoke();
_DisableButtonInteraction();
if (_contentChangedToken != 0)
{
UnregisterPropertyChangedCallback(ContentControl::ContentProperty(), _contentChangedToken);
_contentChangedToken = 0;
}
_UpdateActionIconVisibility();
_UpdateHeaderVisibility();
_UpdateDescriptionVisibility();
_UpdateHeaderIconVisibility();
// Initial visual states.
_CheckInitialVisualState();
_CheckHeaderIconState();
_SetAccessibleContentName();
// Watch for Content changing later (we may need to refresh the AutomationProperties.Name on it).
_contentChangedToken = RegisterPropertyChangedCallback(ContentControl::ContentProperty(), [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_SetAccessibleContentName();
}
});
// Apply click-interaction state.
if (IsClickEnabled())
{
_EnableButtonInteraction();
}
_isEnabledChangedRevoker = IsEnabledChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_GoToCommonState(strongThis->IsEnabled() ? NormalState : DisabledState, true);
strongThis->_CheckHeaderIconState();
}
});
}
void SettingsCard::_CheckInitialVisualState()
{
VisualStateManager::GoToState(*this, IsEnabled() ? hstring{ NormalState } : hstring{ DisabledState }, true);
_UpdateContentAlignmentState();
// Subscribe to ContentAlignmentStates so we can drive ContentSpacingStates
// whenever the alignment shifts to a stacked layout (Vertical, RightWrapped*).
if (const auto child{ GetTemplateChild(hstring{ ContentAlignmentStatesGroup }) })
{
if (const auto group{ child.try_as<VisualStateGroup>() })
{
_CheckVerticalSpacingState(group.CurrentState());
_contentAlignmentStatesChangedRevoker = group.CurrentStateChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, const VisualStateChangedEventArgs& args) {
if (const auto strongThis = weakThis.get())
{
strongThis->_CheckVerticalSpacingState(args.NewState());
}
});
}
}
}
void SettingsCard::_CheckHeaderIconState()
{
// The Disabled common state recolors text/glyph foregrounds via the brush, but a
// BitmapIcon is an image and won't pick up the disabled brush. Lower its opacity
// instead, via the BitmapHeaderIconStates group. Mirrors the toolkit's
// SettingsCard.cs::CheckHeaderIconState.
if (HeaderIcon().try_as<BitmapIcon>())
{
VisualStateManager::GoToState(*this,
hstring{ IsEnabled() ? BitmapHeaderIconEnabledState : BitmapHeaderIconDisabledState },
true);
}
else
{
// Reset to the enabled state when a non-bitmap icon (or none) is present so the
// opacity setter doesn't stick around from a previous bitmap icon.
VisualStateManager::GoToState(*this, hstring{ BitmapHeaderIconEnabledState }, true);
}
}
void SettingsCard::_CheckVerticalSpacingState(const VisualState& state)
{
// Add row spacing whenever the content sits below the header (Vertical or RightWrapped*)
// AND there's both Content and (Header or Description) to space apart.
const auto stateName{ state ? state.Name() : hstring{} };
const bool stackedLayout =
stateName == VerticalState ||
stateName == L"RightWrapped" ||
stateName == L"RightWrappedNoIcon";
const bool hasContent{ static_cast<bool>(Content()) };
const bool hasHeaderOrDescription = !_isNullOrEmpty(Header()) || !_isNullOrEmpty(Description());
VisualStateManager::GoToState(*this,
hstring{ (stackedLayout && hasContent && hasHeaderOrDescription) ? ContentSpacingState : NoContentSpacingState },
true);
}
void SettingsCard::_SetAccessibleContentName()
{
// If Header is a string and the inner Content lacks an AutomationProperties.Name, propagate the header
// into the content so screen readers can announce something meaningful when focus lands there.
const auto headerObj{ Header() };
if (!headerObj)
{
return;
}
const auto headerString{ unbox_value_or<hstring>(headerObj, hstring{}) };
if (headerString.empty())
{
return;
}
const auto contentObj{ Content() };
if (const auto element{ contentObj.try_as<UIElement>() })
{
if (Automation::AutomationProperties::GetName(element).empty())
{
// Don't override ButtonBase content (would clobber its own name) or plain text blocks.
if (!element.try_as<ButtonBase>() && !element.try_as<TextBlock>())
{
Automation::AutomationProperties::SetName(element, headerString);
}
}
}
}
void SettingsCard::_EnableButtonInteraction()
{
if (_interactionEnabled)
{
return;
}
_interactionEnabled = true;
IsTabStop(true);
_pointerEnteredRevoker = PointerEntered(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_GoToCommonState(PointerOverState, true);
}
});
_pointerExitedRevoker = PointerExited(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_GoToCommonState(NormalState, true);
}
});
_pointerCaptureLostRevoker = PointerCaptureLost(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_GoToCommonState(NormalState, true);
}
});
_pointerCanceledRevoker = PointerCanceled(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (const auto strongThis = weakThis.get())
{
strongThis->_GoToCommonState(NormalState, true);
}
});
_previewKeyDownRevoker = PreviewKeyDown(winrt::auto_revoke, [weakThis = get_weak()](auto&&, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e) {
const auto strongThis = weakThis.get();
if (!strongThis)
{
return;
}
const auto key = e.Key();
if (key == Windows::System::VirtualKey::Enter || key == Windows::System::VirtualKey::Space || key == Windows::System::VirtualKey::GamepadA)
{
const auto focused{ strongThis->_GetFocusedElement() };
if (focused && focused.try_as<Editor::SettingsCard>() == strongThis.as<Editor::SettingsCard>())
{
strongThis->_GoToCommonState(PressedState, true);
}
}
});
_previewKeyUpRevoker = PreviewKeyUp(winrt::auto_revoke, [weakThis = get_weak()](auto&&, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e) {
const auto strongThis = weakThis.get();
if (!strongThis)
{
return;
}
const auto key = e.Key();
if (key == Windows::System::VirtualKey::Enter || key == Windows::System::VirtualKey::Space || key == Windows::System::VirtualKey::GamepadA)
{
strongThis->_GoToCommonState(NormalState, true);
}
});
}
void SettingsCard::_DisableButtonInteraction()
{
_interactionEnabled = false;
IsTabStop(false);
_pointerEnteredRevoker.revoke();
_pointerExitedRevoker.revoke();
_pointerCaptureLostRevoker.revoke();
_pointerCanceledRevoker.revoke();
_previewKeyDownRevoker.revoke();
_previewKeyUpRevoker.revoke();
}
void SettingsCard::_GoToCommonState(const std::wstring_view& state, bool useTransitions)
{
VisualStateManager::GoToState(*this, hstring{ state }, useTransitions);
}
FrameworkElement SettingsCard::_GetFocusedElement()
{
if (const auto root{ XamlRoot() })
{
return FocusManager::GetFocusedElement(root).try_as<FrameworkElement>();
}
return FocusManager::GetFocusedElement().try_as<FrameworkElement>();
}
void SettingsCard::_UpdateActionIconVisibility()
{
if (const auto child{ GetTemplateChild(hstring{ ActionIconPresenterHolder }) })
{
if (const auto frameworkChild{ child.try_as<FrameworkElement>() })
{
frameworkChild.Visibility((IsClickEnabled() && IsActionIconVisible()) ? Visibility::Visible : Visibility::Collapsed);
}
}
}
void SettingsCard::_UpdateHeaderVisibility()
{
if (const auto child{ GetTemplateChild(hstring{ HeaderPresenter }) })
{
if (const auto frameworkChild{ child.try_as<FrameworkElement>() })
{
frameworkChild.Visibility(_isNullOrEmpty(Header()) ? Visibility::Collapsed : Visibility::Visible);
}
}
}
void SettingsCard::_UpdateDescriptionVisibility()
{
if (const auto child{ GetTemplateChild(hstring{ DescriptionPresenter }) })
{
if (const auto frameworkChild{ child.try_as<FrameworkElement>() })
{
frameworkChild.Visibility(_isNullOrEmpty(Description()) ? Visibility::Collapsed : Visibility::Visible);
}
}
}
void SettingsCard::_UpdateHeaderIconVisibility()
{
if (const auto child{ GetTemplateChild(hstring{ HeaderIconPresenterHolder }) })
{
if (const auto frameworkChild{ child.try_as<FrameworkElement>() })
{
frameworkChild.Visibility(HeaderIcon() ? Visibility::Visible : Visibility::Collapsed);
}
}
}
void SettingsCard::_UpdateContentAlignmentState()
{
std::wstring_view state{ RightState };
switch (ContentAlignment())
{
case Editor::SettingsCardContentAlignment::Left:
state = LeftState;
break;
case Editor::SettingsCardContentAlignment::Vertical:
state = VerticalState;
break;
default:
state = RightState;
break;
}
VisualStateManager::GoToState(*this, hstring{ state }, true);
}
void SettingsCard::_OnHeaderChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
const auto self = get_self<SettingsCard>(obj);
self->_UpdateHeaderVisibility();
self->_SetAccessibleContentName();
}
void SettingsCard::_OnDescriptionChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
get_self<SettingsCard>(obj)->_UpdateDescriptionVisibility();
}
void SettingsCard::_OnHeaderIconChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
const auto self = get_self<SettingsCard>(obj);
self->_UpdateHeaderIconVisibility();
// HeaderIcon type may have flipped between BitmapIcon and other icon types — re-evaluate
// the BitmapHeaderIcon visual state so the disabled-opacity setter is applied (or cleared).
self->_CheckHeaderIconState();
}
void SettingsCard::_OnIsClickEnabledChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
const auto self = get_self<SettingsCard>(obj);
self->_UpdateActionIconVisibility();
if (self->IsClickEnabled())
{
self->_EnableButtonInteraction();
}
else
{
self->_DisableButtonInteraction();
}
}
void SettingsCard::_OnIsActionIconVisibleChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
get_self<SettingsCard>(obj)->_UpdateActionIconVisibility();
}
void SettingsCard::_OnContentAlignmentChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
const auto obj{ d.try_as<Editor::SettingsCard>() };
get_self<SettingsCard>(obj)->_UpdateContentAlignmentState();
}
SettingsCardAutomationPeer::SettingsCardAutomationPeer(const Editor::SettingsCard& owner) :
SettingsCardAutomationPeerT<SettingsCardAutomationPeer>(owner)
{
}
AutomationControlType SettingsCardAutomationPeer::GetAutomationControlTypeCore() const
{
if (const auto card{ Owner().try_as<Editor::SettingsCard>() })
{
if (card.IsClickEnabled())
{
return AutomationControlType::Button;
}
}
return AutomationControlType::Group;
}
hstring SettingsCardAutomationPeer::GetClassNameCore() const
{
return hstring{ L"SettingsCard" };
}
hstring SettingsCardAutomationPeer::GetNameCore() const
{
if (const auto card{ Owner().try_as<Editor::SettingsCard>() })
{
if (card.IsClickEnabled())
{
if (const auto manualName{ AutomationProperties::GetName(card) }; !manualName.empty())
{
return manualName;
}
if (const auto headerString{ unbox_value_or<hstring>(card.Header(), hstring{}) }; !headerString.empty())
{
return headerString;
}
}
// Not clickable, or no header text: fall back to AutomationProperties.Name (matching
// FrameworkElementAutomationPeer's default behavior).
return AutomationProperties::GetName(card);
}
return {};
}
winrt::Windows::Foundation::IInspectable SettingsCardAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
{
if (patternInterface == PatternInterface::Invoke)
{
if (const auto card{ Owner().try_as<Editor::SettingsCard>() })
{
if (card.IsClickEnabled())
{
// Only provide Invoke pattern if the card is clickable.
return *this;
}
}
return nullptr;
}
// ButtonBaseAutomationPeer only provides Invoke; everything else returns null.
return nullptr;
}
}

View File

@@ -0,0 +1,100 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- SettingsCard
Abstract:
- A base control for building consistent settings experiences. Based
on the Windows Community Toolkit's SettingsCard.
Author(s):
- Carlos Zamora - 2026 May
--*/
#pragma once
#include "SettingsCard.g.h"
#include "SettingsCardAutomationPeer.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct SettingsCard : SettingsCardT<SettingsCard>
{
public:
SettingsCard();
void OnApplyTemplate();
void OnPointerPressed(const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void OnPointerReleased(const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
// Automation peer override.
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Header);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Description);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::IconElement, HeaderIcon);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ActionIcon);
DEPENDENCY_PROPERTY(hstring, ActionIconToolTip);
DEPENDENCY_PROPERTY(bool, IsClickEnabled);
DEPENDENCY_PROPERTY(bool, IsActionIconVisible);
DEPENDENCY_PROPERTY(Editor::SettingsCardContentAlignment, ContentAlignment);
private:
static void _InitializeProperties();
static void _OnHeaderChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnDescriptionChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnHeaderIconChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnIsClickEnabledChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnIsActionIconVisibleChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnContentAlignmentChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
void _EnableButtonInteraction();
void _DisableButtonInteraction();
void _GoToCommonState(const std::wstring_view& state, bool useTransitions);
void _UpdateActionIconVisibility();
void _UpdateHeaderVisibility();
void _UpdateDescriptionVisibility();
void _UpdateHeaderIconVisibility();
void _UpdateContentAlignmentState();
void _CheckInitialVisualState();
void _CheckHeaderIconState();
void _CheckVerticalSpacingState(const Windows::UI::Xaml::VisualState& state);
void _SetAccessibleContentName();
Windows::UI::Xaml::FrameworkElement _GetFocusedElement();
bool _interactionEnabled{ false };
Windows::UI::Xaml::Controls::Control::IsEnabledChanged_revoker _isEnabledChangedRevoker;
Windows::UI::Xaml::UIElement::PointerEntered_revoker _pointerEnteredRevoker;
Windows::UI::Xaml::UIElement::PointerExited_revoker _pointerExitedRevoker;
Windows::UI::Xaml::UIElement::PointerCaptureLost_revoker _pointerCaptureLostRevoker;
Windows::UI::Xaml::UIElement::PointerCanceled_revoker _pointerCanceledRevoker;
Windows::UI::Xaml::UIElement::PreviewKeyDown_revoker _previewKeyDownRevoker;
Windows::UI::Xaml::UIElement::PreviewKeyUp_revoker _previewKeyUpRevoker;
Windows::UI::Xaml::VisualStateGroup::CurrentStateChanged_revoker _contentAlignmentStatesChangedRevoker;
int64_t _contentChangedToken{ 0 };
};
// AutomationPeer for SettingsCard. Mirrors the Community Toolkit's
// SettingsCardAutomationPeer: only exposes Invoke + Button control type when
// the card has IsClickEnabled=true; otherwise reports as a Group.
struct SettingsCardAutomationPeer : SettingsCardAutomationPeerT<SettingsCardAutomationPeer>
{
public:
SettingsCardAutomationPeer(const Editor::SettingsCard& owner);
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
hstring GetClassNameCore() const;
hstring GetNameCore() const;
winrt::Windows::Foundation::IInspectable GetPatternCore(Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(SettingsCard);
BASIC_FACTORY(SettingsCardAutomationPeer);
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
enum SettingsCardContentAlignment
{
Right,
Left,
Vertical
};
[default_interface] runtimeclass SettingsCard : Windows.UI.Xaml.Controls.Primitives.ButtonBase
{
SettingsCard();
Object Header;
static Windows.UI.Xaml.DependencyProperty HeaderProperty { get; };
Object Description;
static Windows.UI.Xaml.DependencyProperty DescriptionProperty { get; };
Windows.UI.Xaml.Controls.IconElement HeaderIcon;
static Windows.UI.Xaml.DependencyProperty HeaderIconProperty { get; };
Object ActionIcon;
static Windows.UI.Xaml.DependencyProperty ActionIconProperty { get; };
String ActionIconToolTip;
static Windows.UI.Xaml.DependencyProperty ActionIconToolTipProperty { get; };
Boolean IsClickEnabled;
static Windows.UI.Xaml.DependencyProperty IsClickEnabledProperty { get; };
Boolean IsActionIconVisible;
static Windows.UI.Xaml.DependencyProperty IsActionIconVisibleProperty { get; };
SettingsCardContentAlignment ContentAlignment;
static Windows.UI.Xaml.DependencyProperty ContentAlignmentProperty { get; };
};
[default_interface] runtimeclass SettingsCardAutomationPeer : Windows.UI.Xaml.Automation.Peers.ButtonBaseAutomationPeer
{
SettingsCardAutomationPeer(SettingsCard owner);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "SettingsExpander.h"
#include "SettingsExpander.g.cpp"
#include "SettingsExpanderAutomationPeer.g.cpp"
#include "SettingsExpanderItemStyleSelector.g.cpp"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Automation;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Xaml::Controls;
namespace MUXC = winrt::Microsoft::UI::Xaml::Controls;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DependencyProperty SettingsExpander::_HeaderProperty{ nullptr };
DependencyProperty SettingsExpander::_DescriptionProperty{ nullptr };
DependencyProperty SettingsExpander::_HeaderIconProperty{ nullptr };
DependencyProperty SettingsExpander::_ContentProperty{ nullptr };
DependencyProperty SettingsExpander::_IsExpandedProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemsHeaderProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemsFooterProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemsProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemsSourceProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemTemplateProperty{ nullptr };
DependencyProperty SettingsExpander::_ItemContainerStyleSelectorProperty{ nullptr };
static constexpr std::wstring_view PART_ItemsRepeater{ L"PART_ItemsRepeater" };
SettingsExpander::SettingsExpander()
{
_InitializeProperties();
Items(single_threaded_vector<IInspectable>());
}
void SettingsExpander::_InitializeProperties()
{
if (!_HeaderProperty)
{
_HeaderProperty = DependencyProperty::Register(
L"Header",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_DescriptionProperty)
{
_DescriptionProperty = DependencyProperty::Register(
L"Description",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_HeaderIconProperty)
{
_HeaderIconProperty = DependencyProperty::Register(
L"HeaderIcon",
xaml_typename<IconElement>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_ContentProperty)
{
_ContentProperty = DependencyProperty::Register(
L"Content",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_IsExpandedProperty)
{
_IsExpandedProperty = DependencyProperty::Register(
L"IsExpanded",
xaml_typename<bool>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ box_value(false), PropertyChangedCallback{ &SettingsExpander::_OnIsExpandedChanged } });
}
if (!_ItemsHeaderProperty)
{
_ItemsHeaderProperty = DependencyProperty::Register(
L"ItemsHeader",
xaml_typename<UIElement>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_ItemsFooterProperty)
{
_ItemsFooterProperty = DependencyProperty::Register(
L"ItemsFooter",
xaml_typename<UIElement>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_ItemsProperty)
{
_ItemsProperty = DependencyProperty::Register(
L"Items",
xaml_typename<IVector<IInspectable>>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
}
if (!_ItemsSourceProperty)
{
_ItemsSourceProperty = DependencyProperty::Register(
L"ItemsSource",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
}
if (!_ItemTemplateProperty)
{
_ItemTemplateProperty = DependencyProperty::Register(
L"ItemTemplate",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
if (!_ItemContainerStyleSelectorProperty)
{
_ItemContainerStyleSelectorProperty = DependencyProperty::Register(
L"ItemContainerStyleSelector",
xaml_typename<StyleSelector>(),
xaml_typename<Editor::SettingsExpander>(),
PropertyMetadata{ nullptr });
}
}
AutomationPeer SettingsExpander::OnCreateAutomationPeer()
{
return winrt::make<implementation::SettingsExpanderAutomationPeer>(*this);
}
void SettingsExpander::OnApplyTemplate()
{
_SetAccessibleName();
// Drop the prior template's repeater hookups before locating the new one.
_elementPreparedRevoker.revoke();
_itemsRepeater = nullptr;
if (const auto child{ GetTemplateChild(hstring{ PART_ItemsRepeater }) })
{
_itemsRepeater = child.try_as<MUXC::ItemsRepeater>();
}
if (_itemsRepeater)
{
_elementPreparedRevoker = _itemsRepeater.ElementPrepared(winrt::auto_revoke, { get_weak(), &SettingsExpander::_ItemsRepeater_ElementPrepared });
// Push our initial ItemsSource through to the repeater.
_UpdateItemsSource();
}
}
void SettingsExpander::_SetAccessibleName()
{
if (!AutomationProperties::GetName(*this).empty())
{
return;
}
if (const auto headerString{ unbox_value_or<hstring>(Header(), hstring{}) }; !headerString.empty())
{
AutomationProperties::SetName(*this, headerString);
}
}
void SettingsExpander::_UpdateItemsSource()
{
if (!_itemsRepeater)
{
return;
}
// ItemsSource wins when set; otherwise fall back to the inline Items collection.
if (const auto source{ ItemsSource() })
{
_itemsRepeater.ItemsSource(source);
}
else
{
_itemsRepeater.ItemsSource(Items());
}
}
void SettingsExpander::_OnItemsConnectedPropertyChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
if (const auto obj{ d.try_as<Editor::SettingsExpander>() })
{
get_self<SettingsExpander>(obj)->_UpdateItemsSource();
}
}
void SettingsExpander::_ItemsRepeater_ElementPrepared(const MUXC::ItemsRepeater& /*sender*/, const MUXC::ItemsRepeaterElementPreparedEventArgs& args)
{
const auto selector{ ItemContainerStyleSelector() };
if (!selector)
{
return;
}
if (const auto element{ args.Element().try_as<FrameworkElement>() })
{
if (element.ReadLocalValue(FrameworkElement::StyleProperty()) == DependencyProperty::UnsetValue())
{
element.Style(selector.SelectStyle(nullptr, element));
}
}
}
void SettingsExpander::_OnIsExpandedChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& e)
{
const auto obj{ d.try_as<Editor::SettingsExpander>() };
if (!obj)
{
return;
}
const auto self = get_self<SettingsExpander>(obj);
const auto newValue = unbox_value_or<bool>(e.NewValue(), false);
// Notify the automation peer so screen readers see the expand/collapse state change.
if (const auto peer{ FrameworkElementAutomationPeer::FromElement(obj).try_as<Editor::SettingsExpanderAutomationPeer>() })
{
peer.RaiseExpandedChangedEvent(newValue);
}
if (newValue)
{
self->Expanded.raise(obj, nullptr);
}
else
{
self->Collapsed.raise(obj, nullptr);
}
}
SettingsExpanderAutomationPeer::SettingsExpanderAutomationPeer(const Editor::SettingsExpander& owner) :
SettingsExpanderAutomationPeerT<SettingsExpanderAutomationPeer>(owner)
{
}
AutomationControlType SettingsExpanderAutomationPeer::GetAutomationControlTypeCore() const
{
return AutomationControlType::Group;
}
hstring SettingsExpanderAutomationPeer::GetClassNameCore() const
{
return hstring{ L"SettingsExpander" };
}
hstring SettingsExpanderAutomationPeer::GetNameCore() const
{
if (const auto expander{ Owner().try_as<Editor::SettingsExpander>() })
{
if (const auto manualName{ AutomationProperties::GetName(expander) }; !manualName.empty())
{
return manualName;
}
if (const auto headerString{ unbox_value_or<hstring>(expander.Header(), hstring{}) }; !headerString.empty())
{
return headerString;
}
}
return {};
}
void SettingsExpanderAutomationPeer::RaiseExpandedChangedEvent(bool newValue)
{
// Mirrors the toolkit's SettingsExpanderAutomationPeer.RaiseExpandedChangedEvent.
// Narrator doesn't actually announce this today (microsoft/microsoft-ui-xaml#3469),
// but other AT can subscribe and we keep parity with the toolkit.
const auto newState = newValue ? ExpandCollapseState::Expanded : ExpandCollapseState::Collapsed;
const auto oldState = newValue ? ExpandCollapseState::Collapsed : ExpandCollapseState::Expanded;
RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers::ExpandCollapseStateProperty(), box_value(oldState), box_value(newState));
}
Windows::UI::Xaml::Style SettingsExpanderItemStyleSelector::SelectStyleCore(const winrt::Windows::Foundation::IInspectable& /*item*/, const Windows::UI::Xaml::DependencyObject& container)
{
// When the prepared container is a clickable SettingsCard, give it the
// clickable item style (which adds the right padding for the chevron).
// Otherwise fall back to the default item style.
if (const auto card{ container.try_as<Editor::SettingsCard>() })
{
if (card.IsClickEnabled())
{
return _ClickableStyle;
}
}
return _DefaultStyle;
}
}

View File

@@ -0,0 +1,96 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- SettingsExpander
Abstract:
- A collapsable container for grouping multiple SettingsCards. Based
on the Windows Community Toolkit's SettingsExpander.
Author(s):
- Carlos Zamora - May 2026 (port from CommunityToolkit.WinUI.Controls.SettingsExpander)
--*/
#pragma once
#include "SettingsExpander.g.h"
#include "SettingsExpanderAutomationPeer.g.h"
#include "SettingsExpanderItemStyleSelector.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct SettingsExpander : SettingsExpanderT<SettingsExpander>
{
public:
SettingsExpander();
void OnApplyTemplate();
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
til::typed_event<Editor::SettingsExpander, Windows::Foundation::IInspectable> Expanded;
til::typed_event<Editor::SettingsExpander, Windows::Foundation::IInspectable> Collapsed;
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Header);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Description);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::IconElement, HeaderIcon);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Content);
DEPENDENCY_PROPERTY(bool, IsExpanded);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsHeader);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsFooter);
DEPENDENCY_PROPERTY(Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable>, Items);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemsSource);
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemTemplate);
DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::StyleSelector, ItemContainerStyleSelector);
private:
static void _InitializeProperties();
static void _OnIsExpandedChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
static void _OnItemsConnectedPropertyChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
void _SetAccessibleName();
void _UpdateItemsSource();
void _ItemsRepeater_ElementPrepared(const Microsoft::UI::Xaml::Controls::ItemsRepeater& sender, const Microsoft::UI::Xaml::Controls::ItemsRepeaterElementPreparedEventArgs& args);
Microsoft::UI::Xaml::Controls::ItemsRepeater _itemsRepeater{ nullptr };
Microsoft::UI::Xaml::Controls::ItemsRepeater::ElementPrepared_revoker _elementPreparedRevoker;
};
// AutomationPeer for SettingsExpander. Reports class name and falls back to
// Header text for the name when AutomationProperties.Name is unset.
struct SettingsExpanderAutomationPeer : SettingsExpanderAutomationPeerT<SettingsExpanderAutomationPeer>
{
public:
SettingsExpanderAutomationPeer(const Editor::SettingsExpander& owner);
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
hstring GetClassNameCore() const;
hstring GetNameCore() const;
void RaiseExpandedChangedEvent(bool newValue);
};
// StyleSelector used by SettingsExpander to choose between a clickable vs.
// non-clickable SettingsCard container style for items in its ItemsRepeater.
// Ported from the Windows Community Toolkit's SettingsExpanderItemStyleSelector.
struct SettingsExpanderItemStyleSelector : SettingsExpanderItemStyleSelectorT<SettingsExpanderItemStyleSelector>
{
SettingsExpanderItemStyleSelector() = default;
Windows::UI::Xaml::Style SelectStyleCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
WINRT_PROPERTY(Windows::UI::Xaml::Style, DefaultStyle, nullptr);
WINRT_PROPERTY(Windows::UI::Xaml::Style, ClickableStyle, nullptr);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(SettingsExpander);
BASIC_FACTORY(SettingsExpanderAutomationPeer);
BASIC_FACTORY(SettingsExpanderItemStyleSelector);
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass SettingsExpander : Windows.UI.Xaml.Controls.Control
{
SettingsExpander();
Object Header;
static Windows.UI.Xaml.DependencyProperty HeaderProperty { get; };
Object Description;
static Windows.UI.Xaml.DependencyProperty DescriptionProperty { get; };
Windows.UI.Xaml.Controls.IconElement HeaderIcon;
static Windows.UI.Xaml.DependencyProperty HeaderIconProperty { get; };
Object Content;
static Windows.UI.Xaml.DependencyProperty ContentProperty { get; };
Boolean IsExpanded;
static Windows.UI.Xaml.DependencyProperty IsExpandedProperty { get; };
Windows.UI.Xaml.UIElement ItemsHeader;
static Windows.UI.Xaml.DependencyProperty ItemsHeaderProperty { get; };
Windows.UI.Xaml.UIElement ItemsFooter;
static Windows.UI.Xaml.DependencyProperty ItemsFooterProperty { get; };
Windows.Foundation.Collections.IVector<Object> Items;
static Windows.UI.Xaml.DependencyProperty ItemsProperty { get; };
Object ItemsSource;
static Windows.UI.Xaml.DependencyProperty ItemsSourceProperty { get; };
Object ItemTemplate;
static Windows.UI.Xaml.DependencyProperty ItemTemplateProperty { get; };
Windows.UI.Xaml.Controls.StyleSelector ItemContainerStyleSelector;
static Windows.UI.Xaml.DependencyProperty ItemContainerStyleSelectorProperty { get; };
event Windows.Foundation.TypedEventHandler<SettingsExpander, Object> Expanded;
event Windows.Foundation.TypedEventHandler<SettingsExpander, Object> Collapsed;
};
[default_interface] runtimeclass SettingsExpanderAutomationPeer : Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer
{
SettingsExpanderAutomationPeer(SettingsExpander owner);
void RaiseExpandedChangedEvent(Boolean newValue);
};
[default_interface] runtimeclass SettingsExpanderItemStyleSelector : Windows.UI.Xaml.Controls.StyleSelector
{
SettingsExpanderItemStyleSelector();
Windows.UI.Xaml.Style DefaultStyle;
Windows.UI.Xaml.Style ClickableStyle;
};
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "StringDefaultTemplateSelector.h"
#include "StringDefaultTemplateSelector.g.cpp"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DataTemplate StringDefaultTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/)
{
return SelectTemplateCore(item);
}
DataTemplate StringDefaultTemplateSelector::SelectTemplateCore(const IInspectable& item)
{
if (const auto pv{ item.try_as<IPropertyValue>() }; pv && pv.Type() == PropertyType::String)
{
return _StringTemplate;
}
return nullptr;
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
#pragma once
#include "StringDefaultTemplateSelector.g.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct StringDefaultTemplateSelector : StringDefaultTemplateSelectorT<StringDefaultTemplateSelector>
{
StringDefaultTemplateSelector() = default;
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item, const winrt::Windows::UI::Xaml::DependencyObject& container);
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, StringTemplate, nullptr);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(StringDefaultTemplateSelector);
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass StringDefaultTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
{
StringDefaultTemplateSelector();
Windows.UI.Xaml.DataTemplate StringTemplate;
}
}

View File

@@ -73,8 +73,6 @@ static constexpr std::string_view NewWindowKey{ "newWindow" };
static constexpr std::string_view IdentifyWindowKey{ "identifyWindow" };
static constexpr std::string_view IdentifyWindowsKey{ "identifyWindows" };
static constexpr std::string_view RenameWindowKey{ "renameWindow" };
static constexpr std::string_view OpenWorkspaceKey{ "openWorkspace" };
static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
static constexpr std::string_view DisplayWorkingDirectoryKey{ "debugTerminalCwd" };
static constexpr std::string_view SearchForTextKey{ "searchWeb" };
@@ -103,7 +101,6 @@ static constexpr std::string_view OpenScratchpadKey{ "experimental.openScratchpa
static constexpr std::string_view OpenAboutKey{ "openAbout" };
static constexpr std::string_view QuickFixKey{ "quickFix" };
static constexpr std::string_view OpenCWDKey{ "openCWD" };
static constexpr std::string_view WorkspacesKey{ "workspaces" };
static constexpr std::string_view ActionKey{ "action" };

View File

@@ -40,7 +40,6 @@
#include "PrevTabArgs.g.cpp"
#include "NextTabArgs.g.cpp"
#include "RenameWindowArgs.g.cpp"
#include "OpenWorkspaceArgs.g.cpp"
#include "SearchForTextArgs.g.cpp"
#include "GlobalSummonArgs.g.cpp"
#include "FocusPaneArgs.g.cpp"
@@ -796,15 +795,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_switchable_(L"ResetWindowNameCommandKey");
}
winrt::hstring OpenWorkspaceArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
{
if (!Name().empty())
{
return winrt::hstring{ RS_switchable_fmt(L"OpenWorkspaceCommandKey", Name()) };
}
return RS_switchable_(L"OpenWorkspaceDefaultCommandKey");
}
winrt::hstring SearchForTextArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
{
if (QueryUrl().empty())

View File

@@ -42,7 +42,6 @@
#include "PrevTabArgs.g.h"
#include "NextTabArgs.g.h"
#include "RenameWindowArgs.g.h"
#include "OpenWorkspaceArgs.g.h"
#include "SearchForTextArgs.g.h"
#include "GlobalSummonArgs.g.h"
#include "FocusPaneArgs.g.h"
@@ -247,10 +246,6 @@ protected: \
#define RENAME_WINDOW_ARGS(X) \
X(winrt::hstring, Name, "name", false, ArgTypeHint::None, L"")
////////////////////////////////////////////////////////////////////////////////
#define OPEN_WORKSPACE_ARGS(X) \
X(winrt::hstring, Name, "name", false, ArgTypeHint::None, L"")
////////////////////////////////////////////////////////////////////////////////
#define SEARCH_FOR_TEXT_ARGS(X) \
X(winrt::hstring, QueryUrl, "queryUrl", false, ArgTypeHint::None, L"")
@@ -945,8 +940,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARGS_STRUCT(RenameWindowArgs, RENAME_WINDOW_ARGS);
ACTION_ARGS_STRUCT(OpenWorkspaceArgs, OPEN_WORKSPACE_ARGS);
ACTION_ARGS_STRUCT(SearchForTextArgs, SEARCH_FOR_TEXT_ARGS);
struct GlobalSummonArgs : public GlobalSummonArgsT<GlobalSummonArgs>
@@ -1066,7 +1059,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(SetMaximizedArgs);
BASIC_FACTORY(SetColorSchemeArgs);
BASIC_FACTORY(RenameWindowArgs);
BASIC_FACTORY(OpenWorkspaceArgs);
BASIC_FACTORY(ExecuteCommandlineArgs);
BASIC_FACTORY(CloseOtherTabsArgs);
BASIC_FACTORY(CloseTabsAfterArgs);

View File

@@ -420,12 +420,6 @@ namespace Microsoft.Terminal.Settings.Model
String Name { get; };
};
[default_interface] runtimeclass OpenWorkspaceArgs : IActionArgs, IActionArgsDescriptorAccess
{
OpenWorkspaceArgs(String name);
String Name { get; };
};
[default_interface] runtimeclass SearchForTextArgs : IActionArgs, IActionArgsDescriptorAccess
{
String QueryUrl { get; };

View File

@@ -162,7 +162,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::TogglePaneZoom, USES_RESOURCE(L"TogglePaneZoomCommandKey") },
{ ShortcutAction::ToggleShaderEffects, USES_RESOURCE(L"ToggleShaderEffectsCommandKey") },
{ ShortcutAction::ToggleSplitOrientation, USES_RESOURCE(L"ToggleSplitOrientationCommandKey") },
{ ShortcutAction::Workspaces, USES_RESOURCE(L"WorkspacesCommandKey") },
};
}();

View File

@@ -113,9 +113,7 @@
ON_ALL_ACTIONS(OpenScratchpad) \
ON_ALL_ACTIONS(OpenAbout) \
ON_ALL_ACTIONS(QuickFix) \
ON_ALL_ACTIONS(OpenCWD) \
ON_ALL_ACTIONS(OpenWorkspace) \
ON_ALL_ACTIONS(Workspaces)
ON_ALL_ACTIONS(OpenCWD)
#define ALL_SHORTCUT_ACTIONS_WITH_ARGS \
ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \
@@ -160,8 +158,7 @@
ON_ALL_ACTIONS_WITH_ARGS(Suggestions) \
ON_ALL_ACTIONS_WITH_ARGS(SelectCommand) \
ON_ALL_ACTIONS_WITH_ARGS(SelectOutput) \
ON_ALL_ACTIONS_WITH_ARGS(ColorSelection) \
ON_ALL_ACTIONS_WITH_ARGS(OpenWorkspace)
ON_ALL_ACTIONS_WITH_ARGS(ColorSelection)
// These two macros here are for actions that we only use as internal currency.
// They don't need to be parsed by the settings model, or saved as actions to

View File

@@ -20,7 +20,6 @@ static constexpr std::string_view TabLayoutKey{ "tabLayout" };
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
static constexpr std::string_view InitialSizeKey{ "initialSize" };
static constexpr std::string_view LaunchModeKey{ "launchMode" };
static constexpr std::string_view PersistedWorkspacesKey{ "persistedWorkspaces" };
namespace Microsoft::Terminal::Settings::Model::JsonUtils
{
@@ -277,10 +276,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually handled because IMap<K,V> has a comma that breaks the X-macro.
if (WI_IsFlagSet(parseSource, FileSource::Local))
state->PersistedWorkspaces = JsonUtils::GetValueForKey<std::optional<Windows::Foundation::Collections::IMap<hstring, Model::WindowLayout>>>(root, PersistedWorkspacesKey);
}
Json::Value ApplicationState::ToJson(FileSource parseSource) const noexcept
@@ -303,10 +298,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually handled because IMap<K,V> has a comma that breaks the X-macro.
if (WI_IsFlagSet(parseSource, FileSource::Local))
JsonUtils::SetValueForKey(root, PersistedWorkspacesKey, state->PersistedWorkspaces);
}
return root;
}
@@ -350,114 +341,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return false;
}
void ApplicationState::SaveWorkspace(const hstring& name, const Model::WindowLayout& layout)
{
{
const auto state = _state.lock();
if (!state->PersistedWorkspaces || !*state->PersistedWorkspaces)
{
state->PersistedWorkspaces = winrt::single_threaded_map<hstring, Model::WindowLayout>();
}
(*state->PersistedWorkspaces).Insert(name, layout);
}
_throttler();
}
bool ApplicationState::RemoveWorkspace(const hstring& name)
{
bool removed{ false };
{
const auto state = _state.lock();
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
{
auto map = *state->PersistedWorkspaces;
if (map.HasKey(name))
{
map.Remove(name);
removed = true;
}
}
}
if (removed)
{
_throttler();
}
return removed;
}
// Method Description:
// - Rename a persisted workspace entry from oldName to newName. If there
// was no entry for oldName, this is a no-op. If an entry for newName
// already exists, it will be overwritten with the layout from oldName.
// Return Value:
// - true if an entry was renamed, false otherwise.
bool ApplicationState::RenameWorkspace(const hstring& oldName, const hstring& newName)
{
if (oldName == newName || oldName.empty() || newName.empty())
{
return false;
}
bool renamed{ false };
{
const auto state = _state.lock();
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
{
auto map = *state->PersistedWorkspaces;
if (map.HasKey(oldName))
{
const auto layout = map.Lookup(oldName);
map.Insert(newName, layout);
map.Remove(oldName);
renamed = true;
}
}
}
if (renamed)
{
_throttler();
}
return renamed;
}
// Method Description:
// - Atomically remove and return a persisted workspace entry. This is the
// intended API for the startup path that restores a named workspace,
// because it guarantees only one caller can claim a given workspace.
// Return Value:
// - The layout that was stored under `name`, or nullptr if there was none.
Model::WindowLayout ApplicationState::TakeWorkspace(const hstring& name)
{
Model::WindowLayout result{ nullptr };
{
const auto state = _state.lock();
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
{
auto map = *state->PersistedWorkspaces;
if (map.HasKey(name))
{
result = map.Lookup(name);
map.Remove(name);
}
}
}
if (result)
{
_throttler();
}
return result;
}
Windows::Foundation::Collections::IMapView<hstring, Model::WindowLayout> ApplicationState::AllPersistedWorkspaces()
{
const auto state = _state.lock_shared();
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
{
return (*state->PersistedWorkspaces).GetView();
}
return nullptr;
}
// Generate all getter/setters
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
type ApplicationState::name() const noexcept \

View File

@@ -16,7 +16,7 @@ Abstract:
#include "WindowLayout.g.h"
#include <inc/cppwinrt_utils.h>
#include "JsonUtils.h"
#include <JsonUtils.h>
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
@@ -75,12 +75,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool DismissBadge(const hstring& badgeId);
bool BadgeDismissed(const hstring& badgeId) const;
void SaveWorkspace(const hstring& name, const Model::WindowLayout& layout);
bool RemoveWorkspace(const hstring& name);
bool RenameWorkspace(const hstring& oldName, const hstring& newName);
Model::WindowLayout TakeWorkspace(const hstring& name);
Windows::Foundation::Collections::IMapView<hstring, Model::WindowLayout> AllPersistedWorkspaces();
// State getters/setters
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
type name() const noexcept; \
@@ -94,8 +88,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually declared because IMap<K,V> has a comma that breaks the macro.
std::optional<Windows::Foundation::Collections::IMap<hstring, Model::WindowLayout>> PersistedWorkspaces;
};
til::shared_mutex<state_t> _state;
std::filesystem::path _sharedPath;

View File

@@ -36,12 +36,6 @@ namespace Microsoft.Terminal.Settings.Model
Boolean DismissBadge(String badgeId);
Boolean BadgeDismissed(String badgeId);
void SaveWorkspace(String name, WindowLayout layout);
Boolean RemoveWorkspace(String name);
Boolean RenameWorkspace(String oldName, String newName);
WindowLayout TakeWorkspace(String name);
Windows.Foundation.Collections.IMapView<String, WindowLayout> AllPersistedWorkspaces();
String SettingsHash;
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts;
Windows.Foundation.Collections.IVector<String> RecentCommands;

View File

@@ -136,7 +136,7 @@ private: \
return std::nullopt; \
} \
\
auto _get##name##OverrideSourceImpl() -> decltype(get_strong()) \
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
{ \
/*we have a value*/ \
if (_##name) \
@@ -159,7 +159,7 @@ private: \
} \
\
auto _get##name##OverrideSourceAndValueImpl() \
-> std::pair<decltype(get_strong()), storageType> \
->std::pair<decltype(get_strong()), storageType> \
{ \
/*we have a value*/ \
if (_##name) \

View File

@@ -161,8 +161,7 @@ Author(s):
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Frame, "frame", nullptr) \
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedFrame, "unfocusedFrame", nullptr) \
X(bool, RainbowFrame, "experimental.rainbowFrame", false) \
X(bool, UseMica, "useMica", false) \
X(bool, ShowWindowsButton, "showWindowsButton", true)
X(bool, UseMica, "useMica", false)
#define MTSM_THEME_SETTINGS_SETTINGS(X) \
X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default)

View File

@@ -518,13 +518,6 @@
<data name="ResetWindowNameCommandKey" xml:space="preserve">
<value>Reset window name</value>
</data>
<data name="OpenWorkspaceCommandKey" xml:space="preserve">
<value>Open workspace "{0}"</value>
<comment>{0} will be replaced with the workspace name</comment>
</data>
<data name="OpenWorkspaceDefaultCommandKey" xml:space="preserve">
<value>Open workspace</value>
</data>
<data name="OpenWindowRenamerCommandKey" xml:space="preserve">
<value>Rename window...</value>
</data>
@@ -747,9 +740,6 @@
<data name="OpenCWDCommandKey" xml:space="preserve">
<value>Open current working directory</value>
</data>
<data name="WorkspacesCommandKey" xml:space="preserve">
<value>Workspaces...</value>
</data>
<data name="CloseTab" xml:space="preserve">
<value>Close tab</value>
</data>

View File

@@ -62,7 +62,6 @@ namespace Microsoft.Terminal.Settings.Model
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
Boolean UseMica { get; };
Boolean RainbowFrame { get; };
Boolean ShowWindowsButton { get; };
ThemeColor Frame { get; };
ThemeColor UnfocusedFrame { get; };
}

View File

@@ -542,7 +542,6 @@
{ "command": "quickFix", "id": "Terminal.QuickFix" },
{ "command": { "action": "showSuggestions", "source": "all"}, "id": "Terminal.Suggestions" },
{ "command": "openCWD", "id": "Terminal.OpenCWD" },
{ "command": "workspaces", "id": "Terminal.Workspaces" },
// Tab Management
// "command": "closeTab" is unbound by default.

View File

@@ -1,127 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ApplicationState.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace SettingsModelUnitTests
{
// Covers the workspace-persistence APIs added to ApplicationState:
// SaveWorkspace / RemoveWorkspace / RenameWorkspace / TakeWorkspace /
// AllPersistedWorkspaces.
// All tests operate on a throw-away ApplicationState instance pointed at
// a temp directory, so they don't touch the real user state.
class ApplicationStateTests
{
TEST_CLASS(ApplicationStateTests);
TEST_METHOD(SaveAndLookupWorkspace);
TEST_METHOD(RemoveWorkspaceReturnsFalseWhenMissing);
TEST_METHOD(RenameWorkspaceMigratesEntry);
TEST_METHOD(RenameWorkspaceNoOpForEmptyOrEqualNames);
TEST_METHOD(RenameWorkspaceNoOpForMissingEntry);
TEST_METHOD(TakeWorkspaceRemovesAndReturns);
TEST_METHOD(TakeWorkspaceReturnsNullWhenMissing);
private:
static std::filesystem::path _tempRoot()
{
auto root = std::filesystem::temp_directory_path() / L"WT_ApplicationStateTests";
std::error_code ec;
std::filesystem::create_directories(root, ec);
// Best-effort clean of any leftover state.json from a prior run so
// tests see an empty starting point.
std::filesystem::remove(root / L"state.json", ec);
std::filesystem::remove(root / L"elevated-state.json", ec);
return root;
}
static winrt::com_ptr<implementation::ApplicationState> _make()
{
return winrt::make_self<implementation::ApplicationState>(_tempRoot());
}
static WindowLayout _makeLayout()
{
WindowLayout layout;
layout.TabLayout(winrt::single_threaded_vector<ActionAndArgs>());
return layout;
}
};
void ApplicationStateTests::SaveAndLookupWorkspace()
{
auto state = _make();
const auto layout = _makeLayout();
state->SaveWorkspace(L"win1", layout);
const auto all = state->AllPersistedWorkspaces();
VERIFY_IS_NOT_NULL(all);
VERIFY_IS_TRUE(all.HasKey(L"win1"));
}
void ApplicationStateTests::RemoveWorkspaceReturnsFalseWhenMissing()
{
auto state = _make();
VERIFY_IS_FALSE(state->RemoveWorkspace(L"does-not-exist"));
state->SaveWorkspace(L"win1", _makeLayout());
VERIFY_IS_TRUE(state->RemoveWorkspace(L"win1"));
VERIFY_IS_FALSE(state->RemoveWorkspace(L"win1"));
}
void ApplicationStateTests::RenameWorkspaceMigratesEntry()
{
auto state = _make();
state->SaveWorkspace(L"oldName", _makeLayout());
VERIFY_IS_TRUE(state->RenameWorkspace(L"oldName", L"newName"));
const auto all = state->AllPersistedWorkspaces();
VERIFY_IS_NOT_NULL(all);
VERIFY_IS_FALSE(all.HasKey(L"oldName"));
VERIFY_IS_TRUE(all.HasKey(L"newName"));
}
void ApplicationStateTests::RenameWorkspaceNoOpForEmptyOrEqualNames()
{
auto state = _make();
state->SaveWorkspace(L"win1", _makeLayout());
VERIFY_IS_FALSE(state->RenameWorkspace(L"win1", L"win1"));
VERIFY_IS_FALSE(state->RenameWorkspace(L"", L"win2"));
VERIFY_IS_FALSE(state->RenameWorkspace(L"win1", L""));
}
void ApplicationStateTests::RenameWorkspaceNoOpForMissingEntry()
{
auto state = _make();
VERIFY_IS_FALSE(state->RenameWorkspace(L"missing", L"newName"));
}
void ApplicationStateTests::TakeWorkspaceRemovesAndReturns()
{
auto state = _make();
state->SaveWorkspace(L"win1", _makeLayout());
const auto taken = state->TakeWorkspace(L"win1");
VERIFY_IS_NOT_NULL(taken);
// Subsequent Take for the same name must return null — this is the
// atomicity guarantee the startup path relies on.
VERIFY_IS_NULL(state->TakeWorkspace(L"win1"));
}
void ApplicationStateTests::TakeWorkspaceReturnsNullWhenMissing()
{
auto state = _make();
VERIFY_IS_NULL(state->TakeWorkspace(L"missing"));
}
}

View File

@@ -45,7 +45,6 @@
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="ThemeTests.cpp" />
<ClCompile Include="MediaResourceTests.cpp" />
<ClCompile Include="ApplicationStateTests.cpp" />
<ClCompile Include="../TerminalSettingsAppAdapterLib/TerminalSettings.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View File

@@ -66,8 +66,6 @@ Author(s):
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/winrt.h>
#include <til/mutex.h>
#include <til/throttled_func.h>
// Common includes for most tests:
#include "../../inc/conattrs.hpp"

View File

@@ -132,12 +132,7 @@ void AppHost::_HandleCommandlineArgs(const winrt::TerminalApp::WindowRequestedAr
// We don't have XAML yet, but we do have other stuff.
_windowLogic = _appLogic.CreateNewWindow();
if (const auto layout = windowArgs.PersistedLayout())
{
_windowLogic.SetPersistedLayout(layout);
_launchShowWindowCommand = SW_NORMAL;
}
else if (const auto content = windowArgs.Content(); !content.empty())
if (const auto content = windowArgs.Content(); !content.empty())
{
_windowLogic.SetStartupContent(content, windowArgs.InitialBounds());
_launchShowWindowCommand = SW_NORMAL;
@@ -270,14 +265,12 @@ void AppHost::Initialize()
_revokers.IsQuakeWindowChanged = _windowLogic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged });
_revokers.SummonWindowRequested = _windowLogic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested });
_revokers.SummonWindowByIdRequested = _windowLogic.SummonWindowByIdRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowByIdRequested });
_revokers.FocusTabRequested = _windowLogic.FocusTabRequested(winrt::auto_revoke, { this, &AppHost::_FocusTabRequested });
_revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu });
_revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll });
_revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
_revokers.RequestWindowList = _windowLogic.RequestWindowList(winrt::auto_revoke, { this, &AppHost::_HandleRequestWindowList });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
@@ -417,28 +410,6 @@ void AppHost::_HandleRequestLaunchPosition(const winrt::Windows::Foundation::IIn
args.Position(_GetWindowLaunchPosition());
}
void AppHost::_HandleRequestWindowList(const winrt::Windows::Foundation::IInspectable& /*sender*/,
winrt::TerminalApp::WindowListRequest args)
{
// Ask the Emperor (on the main thread) for the current window list.
// SendMessage blocks until the message is processed, so this is
// synchronous and the results vector is filled in-place.
std::vector<WindowEmperor::WindowListEntry> entries;
SendMessage(_windowManager->GetMainWindow(),
WindowEmperor::WM_GET_WINDOW_LIST,
0,
reinterpret_cast<LPARAM>(&entries));
auto windowEntries = args.Entries();
for (const auto& entry : entries)
{
winrt::TerminalApp::WindowListEntry w;
w.Id(entry.Id);
w.Name(winrt::hstring{ entry.Name });
windowEntries.Append(w);
}
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
{
LaunchPosition pos{};
@@ -1094,23 +1065,6 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta
HandleSummon(std::move(summonArgs));
}
void AppHost::_SummonWindowByIdRequested(const winrt::Windows::Foundation::IInspectable&,
const winrt::TerminalApp::SummonWindowByIdRequestedArgs& args)
{
// Summon the window by its ID without creating a new tab.
// We look up the target window in WindowEmperor and call HandleSummon directly.
const auto targetId = args.WindowId();
if (auto* targetWindow = _windowManager->GetWindowById(targetId))
{
winrt::TerminalApp::SummonWindowBehavior summonBehavior;
summonBehavior.MoveToCurrentDesktop(false);
summonBehavior.DropdownDuration(0);
summonBehavior.ToMonitor(winrt::TerminalApp::MonitorBehavior::InPlace);
summonBehavior.ToggleVisibility(false); // Do not toggle, just make visible.
targetWindow->HandleSummon(std::move(summonBehavior));
}
}
void AppHost::_FocusTabRequested(const winrt::Windows::Foundation::IInspectable&,
const winrt::TerminalApp::Tab& tab)
{

View File

@@ -91,9 +91,6 @@ private:
void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _SummonWindowByIdRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::SummonWindowByIdRequestedArgs& args);
void _FocusTabRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::Tab& tab);
@@ -136,8 +133,6 @@ private:
void _AppTitleChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
void _HandleRequestLaunchPosition(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::LaunchPositionRequest args);
void _HandleRequestWindowList(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::WindowListRequest args);
// Helper struct. By putting these all into one struct, we can revoke them
// all at once, by assigning _revokers to a fresh Revokers instance. That'll
@@ -159,7 +154,6 @@ private:
winrt::TerminalApp::TerminalWindow::IdentifyWindowsRequested_revoker IdentifyWindowsRequested;
winrt::TerminalApp::TerminalWindow::IsQuakeWindowChanged_revoker IsQuakeWindowChanged;
winrt::TerminalApp::TerminalWindow::SummonWindowRequested_revoker SummonWindowRequested;
winrt::TerminalApp::TerminalWindow::SummonWindowByIdRequested_revoker SummonWindowByIdRequested;
winrt::TerminalApp::TerminalWindow::FocusTabRequested_revoker FocusTabRequested;
winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu;
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
@@ -167,7 +161,6 @@ private:
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
winrt::TerminalApp::TerminalWindow::RequestLaunchPosition_revoker RequestLaunchPosition;
winrt::TerminalApp::TerminalWindow::RequestWindowList_revoker RequestWindowList;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged;

View File

@@ -803,19 +803,6 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg
{
winrt::TerminalApp::WindowRequestedArgs request{ windowId, std::move(args) };
request.WindowName(std::move(windowName));
// If we're opening a named window that doesn't exist yet, atomically
// claim any persisted workspace with that name so we restore it here
// and no subsequent window can pick up the same entry.
const auto& reqName = request.WindowName();
if (!reqName.empty())
{
if (const auto layout = ApplicationState::SharedInstance().TakeWorkspace(reqName))
{
request.PersistedLayout(layout);
}
}
CreateNewWindow(std::move(request));
}
}
@@ -1100,22 +1087,6 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
// anyway (since we threw and exited this message handler) so this at least gives back our
// deterministic window count management.
const auto strong = *it;
// Before destroying a named window, persist its full
// tab/buffer state as a workspace so it can be restored later.
try
{
const auto windowName = strong->Logic().WindowProperties().WindowName();
if (!windowName.empty())
{
if (const auto layout = strong->Logic().GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}
CATCH_LOG();
_windows.erase(it);
try
{
@@ -1143,19 +1114,6 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
host->Logic().IdentifyWindow();
}
return 0;
case WM_GET_WINDOW_LIST:
{
auto* result = reinterpret_cast<std::vector<WindowListEntry>*>(lParam);
if (result)
{
for (const auto& host : _windows)
{
const auto props = host->Logic().WindowProperties();
result->emplace_back(WindowListEntry{ props.WindowId(), std::wstring{ props.WindowName() } });
}
}
return 0;
}
case WM_NOTIFY_FROM_NOTIFICATION_AREA:
switch (LOWORD(lParam))
{

View File

@@ -28,16 +28,6 @@ public:
WM_MESSAGE_BOX_CLOSED,
WM_IDENTIFY_ALL_WINDOWS,
WM_NOTIFY_FROM_NOTIFICATION_AREA,
WM_GET_WINDOW_LIST,
};
// Used by WM_GET_WINDOW_LIST. Callers allocate a vector on their
// stack and pass a pointer through LPARAM; the emperor fills it in
// synchronously via SendMessage.
struct WindowListEntry
{
uint64_t Id;
std::wstring Name;
};
HWND GetMainWindow() const noexcept;

View File

@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til
{
namespace details

View File

@@ -416,7 +416,7 @@ function Invoke-CodeFormat() {
[switch]$IgnoreXaml
)
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -prerelease -find "**\x64\bin\clang-format.exe"
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -find "**\x64\bin\clang-format.exe"
If ([String]::IsNullOrEmpty($clangFormatPath)) {
Write-Error "No Visual Studio-supplied version of clang-format could be found."
}

View File

@@ -2,4 +2,4 @@
rem run clang-format on c++ files
pwsh -noprofile -c "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"
powershell -noprofile "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"