diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index a9ac10d8fa8..0c8b6e19336 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -1129,28 +1129,11 @@ namespace winrt::TerminalApp::implementation } } - // Method Description: - // - Gets the taskbar state value from the last active control - // Return Value: - // - The taskbar state of the last active control - uint64_t AppLogic::GetLastActiveControlTaskbarState() - { - if (_root) - { - return _root->GetLastActiveControlTaskbarState(); - } - return {}; - } - - // Method Description: - // - Gets the taskbar progress value from the last active control - // Return Value: - // - The taskbar progress of the last active control - uint64_t AppLogic::GetLastActiveControlTaskbarProgress() + winrt::TerminalApp::TaskbarState AppLogic::TaskbarState() { if (_root) { - return _root->GetLastActiveControlTaskbarProgress(); + return _root->TaskbarState(); } return {}; } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 6d2e36d9eb7..4131d28f135 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -90,8 +90,7 @@ namespace winrt::TerminalApp::implementation void WindowCloseButtonClicked(); - uint64_t GetLastActiveControlTaskbarState(); - uint64_t GetLastActiveControlTaskbarProgress(); + winrt::TerminalApp::TaskbarState TaskbarState(); winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index bb334d26f47..0b889103899 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -68,8 +68,7 @@ namespace TerminalApp void TitlebarClicked(); void WindowCloseButtonClicked(); - UInt64 GetLastActiveControlTaskbarState(); - UInt64 GetLastActiveControlTaskbarProgress(); + TaskbarState TaskbarState{ get; }; FindTargetWindowResult FindTargetWindow(String[] args); diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 59dacf6b904..45db24870eb 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2548,6 +2548,29 @@ bool Pane::ContainsReadOnly() const return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); } +// Method Description: +// - If we're a parent, place the taskbar state for all our leaves into the +// provided vector. +// - If we're a leaf, place our own state into the vector. +// Arguments: +// - states: a vector that will receive all the states of all leaves in the tree +// Return Value: +// - +void Pane::CollectTaskbarStates(std::vector& states) +{ + if (_IsLeaf()) + { + auto tbState{ winrt::make(_control.TaskbarState(), + _control.TaskbarProgress()) }; + states.push_back(tbState); + } + else + { + _firstChild->CollectTaskbarStates(states); + _secondChild->CollectTaskbarStates(states); + } +} + DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index eed685d322a..34d696340b4 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -21,6 +21,7 @@ #pragma once #include "../../cascadia/inc/cppwinrt_utils.h" +#include "TaskbarState.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -92,6 +93,8 @@ class Pane : public std::enable_shared_from_this bool ContainsReadOnly() const; + void CollectTaskbarStates(std::vector& states); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/TaskbarState.cpp b/src/cascadia/TerminalApp/TaskbarState.cpp new file mode 100644 index 00000000000..183460556cc --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TaskbarState.h" +#include "TaskbarState.g.cpp" + +namespace winrt::TerminalApp::implementation +{ + // Default to unset, 0%. + TaskbarState::TaskbarState() : + TaskbarState(0, 0){}; + + TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) : + _State{ dispatchTypesState }, + _Progress{ progressParam } {} + + uint64_t TaskbarState::Priority() const + { + // This seemingly nonsensical ordering is from + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group + switch (_State) + { + case 0: // Clear = 0, + return 5; + case 1: // Set = 1, + return 3; + case 2: // Error = 2, + return 1; + case 3: // Indeterminate = 3, + return 4; + case 4: // Paused = 4 + return 2; + } + // Here, return 6, to definitely be greater than all the other valid values. + // This should never really happen. + return 6; + } + + int TaskbarState::ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs) + { + return lhs.Priority() < rhs.Priority(); + } + +} diff --git a/src/cascadia/TerminalApp/TaskbarState.h b/src/cascadia/TerminalApp/TaskbarState.h new file mode 100644 index 00000000000..e36b4440cc9 --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "inc/cppwinrt_utils.h" +#include "TaskbarState.g.h" + +// fwdecl unittest classes +namespace TerminalAppLocalTests +{ + class TabTests; +}; + +namespace winrt::TerminalApp::implementation +{ + struct TaskbarState : TaskbarStateT + { + public: + TaskbarState(); + TaskbarState(const uint64_t dispatchTypesState, const uint64_t progress); + + static int ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs); + + uint64_t Priority() const; + + WINRT_PROPERTY(uint64_t, State, 0); + WINRT_PROPERTY(uint64_t, Progress, 0); + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(TaskbarState); +} diff --git a/src/cascadia/TerminalApp/TaskbarState.idl b/src/cascadia/TerminalApp/TaskbarState.idl new file mode 100644 index 00000000000..159242a17b2 --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.idl @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + [default_interface] runtimeclass TaskbarState + { + TaskbarState(); + TaskbarState(UInt64 dispatchTypesState, UInt64 progress); + + UInt64 State{ get; }; + UInt64 Progress{ get; }; + UInt64 Priority { get; }; + } +} diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 25cd7cfa3a0..4d9b5d3dc72 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -90,6 +90,9 @@ TabBase.idl + + TaskbarState.idl + TerminalTab.idl @@ -166,6 +169,9 @@ TabBase.idl + + TaskbarState.idl + TerminalTab.idl @@ -258,6 +264,7 @@ + TerminalPage.xaml diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 3bbf27eaba5..4d49779ade9 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2077,29 +2077,35 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Gets the taskbar state value from the last active control + // - Get the combined taskbar state for the page. This is the combination of + // all the states of all the tabs, which are themselves a combination of + // all their panes. Taskbar states are given a priority based on the rules + // in: + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate + // under "How the Taskbar Button Chooses the Progress Indicator for a Group" + // Arguments: + // - // Return Value: - // - The taskbar state of the last active control - uint64_t TerminalPage::GetLastActiveControlTaskbarState() + // - A TaskbarState object representing the combined taskbar state and + // progress percentage of all our tabs. + winrt::TerminalApp::TaskbarState TerminalPage::TaskbarState() const { - if (auto control{ _GetActiveControl() }) - { - return control.TaskbarState(); - } - return {}; - } + auto state{ winrt::make() }; - // Method Description: - // - Gets the taskbar progress value from the last active control - // Return Value: - // - The taskbar progress of the last active control - uint64_t TerminalPage::GetLastActiveControlTaskbarProgress() - { - if (auto control{ _GetActiveControl() }) + for (const auto& tab : _tabs) { - return control.TaskbarProgress(); + if (auto tabImpl{ _GetTerminalTabImpl(tab) }) + { + auto tabState{ tabImpl->GetCombinedTaskbarState() }; + // lowest priority wins + if (tabState.Priority() < state.Priority()) + { + state = tabState; + } + } } - return {}; + + return state; } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index bb1a6f13e9a..6ec7bd7a02b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -85,8 +85,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::IDialogPresenter DialogPresenter() const; void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); - uint64_t GetLastActiveControlTaskbarState(); - uint64_t GetLastActiveControlTaskbarProgress(); + winrt::TerminalApp::TaskbarState TaskbarState() const; void ShowKeyboardServiceWarning(); winrt::hstring KeyboardServiceDisabledText(); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 0d055782268..7d4c580e9f3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "TaskbarState.idl"; namespace TerminalApp { @@ -42,8 +43,7 @@ namespace TerminalApp void ShowKeyboardServiceWarning(); String KeyboardServiceDisabledText { get; }; - UInt64 GetLastActiveControlTaskbarState(); - UInt64 GetLastActiveControlTaskbarProgress(); + TaskbarState TaskbarState{ get; }; event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index ad4595a1b3a..48cf05d3347 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -177,10 +177,9 @@ namespace winrt::TerminalApp::implementation { lastFocusedControl.Focus(_focusState); - // Update our own progress state, and fire an event signaling + // Update our own progress state. This will fire an event signaling // that our taskbar progress changed. _UpdateProgressState(); - _TaskbarProgressChangedHandlers(lastFocusedControl, nullptr); } // When we gain focus, remove the bell indicator if it is active if (_tabStatus.BellIndicator()) @@ -675,6 +674,26 @@ namespace winrt::TerminalApp::implementation }); } + // Method Description: + // - Get the combined taskbar state for the tab. This is the combination of + // all the states of all our panes. Taskbar states are given a priority + // based on the rules in: + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate + // under "How the Taskbar Button Chooses the Progress Indicator for a + // Group" + // Arguments: + // - + // Return Value: + // - A TaskbarState object representing the combined taskbar state and + // progress percentage of all our panes. + winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const + { + std::vector states; + _rootPane->CollectTaskbarStates(states); + return states.empty() ? winrt::make() : + *std::min_element(states.begin(), states.end(), TerminalApp::implementation::TaskbarState::ComparePriority); + } + // Method Description: // - This should be called on the UI thread. If you don't, then it might // silently do nothing. @@ -690,37 +709,39 @@ namespace winrt::TerminalApp::implementation // - void TerminalTab::_UpdateProgressState() { - if (const auto& activeControl{ GetActiveTerminalControl() }) + const auto state{ GetCombinedTaskbarState() }; + + const auto taskbarState = state.State(); + // The progress of the control changed, but not necessarily the progress of the tab. + // Set the tab's progress ring to the active pane's progress + if (taskbarState > 0) { - const auto taskbarState = activeControl.TaskbarState(); - // The progress of the control changed, but not necessarily the progress of the tab. - // Set the tab's progress ring to the active pane's progress - if (taskbarState > 0) + if (taskbarState == 3) { - if (taskbarState == 3) - { - // 3 is the indeterminate state, set the progress ring as such - _tabStatus.IsProgressRingIndeterminate(true); - } - else - { - // any non-indeterminate state has a value, set the progress ring as such - _tabStatus.IsProgressRingIndeterminate(false); - - const auto progressValue = gsl::narrow(activeControl.TaskbarProgress()); - _tabStatus.ProgressValue(progressValue); - } - // Hide the tab icon (the progress ring is placed over it) - HideIcon(true); - _tabStatus.IsProgressRingActive(true); + // 3 is the indeterminate state, set the progress ring as such + _tabStatus.IsProgressRingIndeterminate(true); } else { - // Show the tab icon - HideIcon(false); - _tabStatus.IsProgressRingActive(false); + // any non-indeterminate state has a value, set the progress ring as such + _tabStatus.IsProgressRingIndeterminate(false); + + const auto progressValue = gsl::narrow(state.Progress()); + _tabStatus.ProgressValue(progressValue); } + // Hide the tab icon (the progress ring is placed over it) + HideIcon(true); + _tabStatus.IsProgressRingActive(true); } + else + { + // Show the tab icon + HideIcon(false); + _tabStatus.IsProgressRingActive(false); + } + + // fire an event signaling that our taskbar progress changed. + _TaskbarProgressChangedHandlers(nullptr, nullptr); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index f3cee75559a..6a9c7808c2c 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -83,6 +83,7 @@ namespace winrt::TerminalApp::implementation void TogglePaneReadOnly(); std::shared_ptr GetActivePane() const; + winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const; winrt::TerminalApp::TerminalTabStatus TabStatus() { diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 092fe8eaeab..5a9735f2992 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -126,13 +126,14 @@ bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, cons // Arguments: // - sender: not used // - args: not used -void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) +void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*args*/) { if (_logic) { - const auto state = gsl::narrow_cast(_logic.GetLastActiveControlTaskbarState()); - const auto progress = gsl::narrow_cast(_logic.GetLastActiveControlTaskbarProgress()); - _window->SetTaskbarProgress(state, progress); + const auto state = _logic.TaskbarState(); + _window->SetTaskbarProgress(gsl::narrow_cast(state.State()), + gsl::narrow_cast(state.Progress())); } } diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 50fd2d156d6..57652788419 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -757,7 +757,12 @@ void IslandWindow::SetTaskbarProgress(const size_t state, const size_t progress) _taskbar->SetProgressValue(_window.get(), progress, 100); break; case 3: - // sets the progress indicator to an indeterminate state + // sets the progress indicator to an indeterminate state. + // FIRST, set the progress to "no progress". That'll clear out any + // progress value from the previous state. Otherwise, a transition + // from (error,x%) or (warning,x%) to indeterminate will leave the + // progress value unchanged, and not show the spinner. + _taskbar->SetProgressState(_window.get(), TBPF_NOPROGRESS); _taskbar->SetProgressState(_window.get(), TBPF_INDETERMINATE); break; case 4: