Combine progress states in the tab, taskbar (#10755)
## Summary of the Pull Request ![background-progress-000](https://user-images.githubusercontent.com/18356694/126653006-3ad2fdae-67ae-4cdb-aa46-25d09217e365.gif) This PR causes the Terminal to combine taskbar states at the tab and window level, according to the [MSDN docs for `SetProgressState`](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). This allows the Terminal's taskbar icon to continue showing progress information, even if you're in a pane/tab that _doesn't_ have progress state. This is helpful for cases where the user may be running a build in one tab, and working on something else in another. ## References * [`SetProgressState`](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) * Progress mega: #6700 ## PR Checklist * [x] Closes #10090 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments This also fixes a related bug where transitioning from the "error" or "warning" state directly to the "indeterminate" state would cause the taskbar icon to get stuck in a bad state. ## Validation Steps Performed <details> <summary><code>progress.cmd</code></summary> ```cmd @echo off setlocal enabledelayedexpansion set _type=3 if (%1) == () ( set _type=3 ) else ( set _type=%1 ) if (%_type%) == (0) ( <NUL set /p =]9;4 echo Cleared progress ) if (%_type%) == (1) ( <NUL set /p =]9;4;1;25 echo Started progress (normal, 25^) ) if (%_type%) == (2) ( <NUL set /p =]9;4;2;50 echo Started progress (error, 50^) ) if (%_type%) == (3) ( @rem start indeterminate progress in the taskbar @rem this `<NUL set /p =` magic will output the text _without a newline_ <NUL set /p =]9;4;3 echo Started progress (indeterminate, {omitted}) ) if (%_type%) == (4) ( <NUL set /p =]9;4;4;75 echo Started progress (warning, 75^) ) ``` </details>
This commit is contained in:
parent
c55888f88d
commit
a14b6f89f6
|
@ -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()
|
||||
winrt::TerminalApp::TaskbarState AppLogic::TaskbarState()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
return _root->GetLastActiveControlTaskbarProgress();
|
||||
return _root->TaskbarState();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -90,8 +90,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
uint64_t GetLastActiveControlTaskbarState();
|
||||
uint64_t GetLastActiveControlTaskbarProgress();
|
||||
winrt::TerminalApp::TaskbarState TaskbarState();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
|
||||
|
|
|
@ -68,8 +68,7 @@ namespace TerminalApp
|
|||
void TitlebarClicked();
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
UInt64 GetLastActiveControlTaskbarState();
|
||||
UInt64 GetLastActiveControlTaskbarProgress();
|
||||
TaskbarState TaskbarState{ get; };
|
||||
|
||||
FindTargetWindowResult FindTargetWindow(String[] args);
|
||||
|
||||
|
|
|
@ -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:
|
||||
// - <none>
|
||||
void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_control.TaskbarState(),
|
||||
_control.TaskbarProgress()) };
|
||||
states.push_back(tbState);
|
||||
}
|
||||
else
|
||||
{
|
||||
_firstChild->CollectTaskbarStates(states);
|
||||
_secondChild->CollectTaskbarStates(states);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "TaskbarState.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
|
@ -92,6 +93,8 @@ public:
|
|||
|
||||
bool ContainsReadOnly() const;
|
||||
|
||||
void CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states);
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
|
45
src/cascadia/TerminalApp/TaskbarState.cpp
Normal file
45
src/cascadia/TerminalApp/TaskbarState.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
34
src/cascadia/TerminalApp/TaskbarState.h
Normal file
34
src/cascadia/TerminalApp/TaskbarState.h
Normal file
|
@ -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<TaskbarState>
|
||||
{
|
||||
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);
|
||||
}
|
15
src/cascadia/TerminalApp/TaskbarState.idl
Normal file
15
src/cascadia/TerminalApp/TaskbarState.idl
Normal file
|
@ -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; };
|
||||
}
|
||||
}
|
|
@ -90,6 +90,9 @@
|
|||
<DependentUpon>TabBase.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TabPaletteItem.h" />
|
||||
<ClInclude Include="TaskbarState.h">
|
||||
<DependentUpon>TaskbarState.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TerminalTab.h">
|
||||
<DependentUpon>TerminalTab.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
|
@ -166,6 +169,9 @@
|
|||
<DependentUpon>TabBase.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TabPaletteItem.cpp" />
|
||||
<ClCompile Include="TaskbarState.cpp">
|
||||
<DependentUpon>TaskbarState.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TerminalTab.cpp">
|
||||
<DependentUpon>TerminalTab.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
@ -258,6 +264,7 @@
|
|||
</Midl>
|
||||
<Midl Include="TabBase.idl" />
|
||||
<Midl Include="TabPaletteItem.idl" />
|
||||
<Midl Include="TaskbarState.idl" />
|
||||
<Midl Include="TerminalTab.idl" />
|
||||
<Midl Include="TerminalPage.idl">
|
||||
<DependentUpon>TerminalPage.xaml</DependentUpon>
|
||||
|
|
|
@ -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:
|
||||
// - <none>
|
||||
// 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<winrt::TerminalApp::implementation::TaskbarState>() };
|
||||
|
||||
// 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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
|
|
|
@ -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:
|
||||
// - <none>
|
||||
// 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<winrt::TerminalApp::TaskbarState> states;
|
||||
_rootPane->CollectTaskbarStates(states);
|
||||
return states.empty() ? winrt::make<winrt::TerminalApp::implementation::TaskbarState>() :
|
||||
*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
|
|||
// - <none>
|
||||
void TerminalTab::_UpdateProgressState()
|
||||
{
|
||||
if (const auto& activeControl{ GetActiveTerminalControl() })
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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 state{ GetCombinedTaskbarState() };
|
||||
|
||||
const auto progressValue = gsl::narrow<uint32_t>(activeControl.TaskbarProgress());
|
||||
_tabStatus.ProgressValue(progressValue);
|
||||
}
|
||||
// Hide the tab icon (the progress ring is placed over it)
|
||||
HideIcon(true);
|
||||
_tabStatus.IsProgressRingActive(true);
|
||||
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)
|
||||
{
|
||||
if (taskbarState == 3)
|
||||
{
|
||||
// 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<uint32_t>(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:
|
||||
|
|
|
@ -83,6 +83,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void TogglePaneReadOnly();
|
||||
std::shared_ptr<Pane> GetActivePane() const;
|
||||
winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const;
|
||||
|
||||
winrt::TerminalApp::TerminalTabStatus TabStatus()
|
||||
{
|
||||
|
|
|
@ -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<size_t>(_logic.GetLastActiveControlTaskbarState());
|
||||
const auto progress = gsl::narrow_cast<size_t>(_logic.GetLastActiveControlTaskbarProgress());
|
||||
_window->SetTaskbarProgress(state, progress);
|
||||
const auto state = _logic.TaskbarState();
|
||||
_window->SetTaskbarProgress(gsl::narrow_cast<size_t>(state.State()),
|
||||
gsl::narrow_cast<size_t>(state.Progress()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -776,7 +776,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:
|
||||
|
|
Loading…
Reference in a new issue