Show indicator of bell in tab (#8637)
When we emit a BEL (visual or audible), show an indicator in the tab header If the tab the BEL is coming from is not focused when the BEL is raised, the indicator in its header will be removed when the tab gains focus. If the tab was already focused when the BEL was emitted, then the indicator goes away after 2 seconds. Closes #8106
This commit is contained in:
parent
acf36d0e4f
commit
02fd7a0c15
|
@ -59,6 +59,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
|
|||
|
||||
// Register an event with the control to have it inform us when it gains focus.
|
||||
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
|
||||
// When our border is tapped, make sure to transfer focus to our control.
|
||||
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
|
||||
|
@ -367,15 +368,18 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
|
|||
auto paneProfile = settings.FindProfile(_profile.value());
|
||||
if (paneProfile)
|
||||
{
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
|
||||
// We don't want to do anything if nothing is set, so check for that first
|
||||
if (static_cast<int>(paneProfile.BellStyle()) != 0)
|
||||
{
|
||||
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
}
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual))
|
||||
{
|
||||
// Bubble this event up to app host, starting with bubbling to the hosting tab
|
||||
_PaneRaiseVisualBellHandlers(nullptr);
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
|
||||
{
|
||||
// Audible is set, play the sound
|
||||
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
}
|
||||
|
||||
// raise the event with the bool value corresponding to the visual flag
|
||||
_PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,6 +398,16 @@ void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable cons
|
|||
_GotFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
// Event Description:
|
||||
// - Called when our control loses focus. We'll use this to trigger our LostFocus
|
||||
// callback. The tab that's hosting us should have registered a callback which
|
||||
// can be used to update its own internal focus state
|
||||
void Pane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
_LostFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Fire our Closed event to tell our parent that we should be removed.
|
||||
// Arguments:
|
||||
|
@ -716,6 +730,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
|||
|
||||
// re-attach our handler for the control's GotFocus event.
|
||||
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
|
||||
// If we're inheriting the "last active" state from one of our children,
|
||||
// focus our control now. This should trigger our own GotFocus event.
|
||||
|
@ -1421,6 +1436,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
|||
// control telling us that it's now focused, we want it telling its new
|
||||
// parent.
|
||||
_gotFocusRevoker.revoke();
|
||||
_lostFocusRevoker.revoke();
|
||||
|
||||
_splitState = actualSplitType;
|
||||
_desiredSplitPosition = 1.0f - splitSize;
|
||||
|
@ -2070,4 +2086,5 @@ std::optional<SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane
|
|||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, 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>);
|
||||
|
|
|
@ -83,7 +83,8 @@ public:
|
|||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
||||
private:
|
||||
struct SnapSizeResult;
|
||||
|
@ -111,6 +112,7 @@ private:
|
|||
winrt::event_token _warningBellToken{ 0 };
|
||||
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
|
||||
std::shared_mutex _createCloseLock{};
|
||||
|
||||
|
@ -144,6 +146,8 @@ private:
|
|||
winrt::Windows::Foundation::IInspectable const& e);
|
||||
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
|
||||
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
|
||||
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace winrt::TerminalApp::implementation
|
|||
OBSERVABLE_GETSET_PROPERTY(double, RenamerMaxWidth, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingActive, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingIndeterminate, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, BellIndicator, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(uint32_t, ProgressValue, _PropertyChangedHandlers);
|
||||
|
||||
private:
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace TerminalApp
|
|||
Double RenamerMaxWidth { get; set; };
|
||||
Boolean IsProgressRingActive { get; set; };
|
||||
Boolean IsProgressRingIndeterminate { get; set; };
|
||||
Boolean BellIndicator { get; set; };
|
||||
UInt32 ProgressValue { get; set; };
|
||||
|
||||
TabHeaderControl();
|
||||
|
|
|
@ -43,6 +43,12 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
<!--We want the progress ring to 'replace' the tab icon, but we don't have control
|
||||
over the tab icon here (the tab view item does) - so we hide the tab icon there
|
||||
and use a negative margin for the progress ring here to put it where the icon would be-->
|
||||
<FontIcon x:Name="HeaderBellIndicator"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind BellIndicator, Mode=OneWay}"
|
||||
Glyph=""
|
||||
FontSize="12"
|
||||
Margin="0,0,8,0"/>
|
||||
<FontIcon x:Name="HeaderZoomIcon"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind IsPaneZoomed, Mode=OneWay}"
|
||||
|
|
|
@ -57,6 +57,22 @@ namespace winrt::TerminalApp::implementation
|
|||
TabViewItem().Header(_headerControl);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the timer for the bell indicator in the tab header fires
|
||||
// - Removes the bell indicator from the tab header
|
||||
// Arguments:
|
||||
// - sender, e: not used
|
||||
void TerminalTab::_BellIndicatorTimerTick(Windows::Foundation::IInspectable const& /*sender*/, Windows::Foundation::IInspectable const& /*e*/)
|
||||
{
|
||||
ShowBellIndicator(false);
|
||||
// Just do a sanity check that the timer still exists before we stop it
|
||||
if (_bellIndicatorTimer.has_value())
|
||||
{
|
||||
_bellIndicatorTimer->Stop();
|
||||
_bellIndicatorTimer = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes a TabViewItem for this Tab instance.
|
||||
// Arguments:
|
||||
|
@ -145,6 +161,11 @@ namespace winrt::TerminalApp::implementation
|
|||
lastFocusedControl.Focus(_focusState);
|
||||
lastFocusedControl.TaskbarProgressChanged();
|
||||
}
|
||||
// When we gain focus, remove the bell indicator if it is active
|
||||
if (_headerControl.BellIndicator())
|
||||
{
|
||||
ShowBellIndicator(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,6 +276,44 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Hide or show the bell indicator in the tab header
|
||||
// Arguments:
|
||||
// - show: if true, we show the indicator; if false, we hide the indicator
|
||||
winrt::fire_and_forget TerminalTab::ShowBellIndicator(const bool show)
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_headerControl.BellIndicator(show);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Activates the timer for the bell indicator in the tab
|
||||
// - Called if a bell raised when the tab already has focus
|
||||
winrt::fire_and_forget TerminalTab::ActivateBellIndicatorTimer()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
if (!tab->_bellIndicatorTimer.has_value())
|
||||
{
|
||||
DispatcherTimer bellIndicatorTimer;
|
||||
bellIndicatorTimer.Interval(std::chrono::milliseconds(2000));
|
||||
bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick });
|
||||
bellIndicatorTimer.Start();
|
||||
tab->_bellIndicatorTimer.emplace(std::move(bellIndicatorTimer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the title string of the last focused terminal control in our tree.
|
||||
// Returns the empty string if there is no such control.
|
||||
|
@ -583,10 +642,30 @@ namespace winrt::TerminalApp::implementation
|
|||
// Do nothing if the Tab's lifetime is expired or pane isn't new.
|
||||
auto tab{ weakThis.get() };
|
||||
|
||||
if (tab && sender != tab->_activePane)
|
||||
if (tab)
|
||||
{
|
||||
tab->_UpdateActivePane(sender);
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
if (sender != tab->_activePane)
|
||||
{
|
||||
tab->_UpdateActivePane(sender);
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
}
|
||||
tab->_focusState = WUX::FocusState::Programmatic;
|
||||
// This tab has gained focus, remove the bell indicator if it is active
|
||||
if (tab->_headerControl.BellIndicator())
|
||||
{
|
||||
tab->ShowBellIndicator(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
|
||||
// Do nothing if the Tab's lifetime is expired or pane isn't new.
|
||||
auto tab{ weakThis.get() };
|
||||
|
||||
if (tab)
|
||||
{
|
||||
// update this tab's focus state
|
||||
tab->_focusState = WUX::FocusState::Unfocused;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -620,10 +699,23 @@ namespace winrt::TerminalApp::implementation
|
|||
// Add a PaneRaiseVisualBell event handler to the Pane. When the pane emits this event,
|
||||
// we need to bubble it all the way to app host. In this part of the chain we bubble it
|
||||
// from the hosting tab to the page.
|
||||
pane->PaneRaiseVisualBell([weakThis](auto&& /*s*/) {
|
||||
pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_TabRaiseVisualBellHandlers();
|
||||
if (visual)
|
||||
{
|
||||
tab->_TabRaiseVisualBellHandlers();
|
||||
|
||||
tab->ShowBellIndicator(true);
|
||||
|
||||
// If this tab is focused, activate the bell indicator timer, which will
|
||||
// remove the bell indicator once it fires
|
||||
// (otherwise, the indicator is removed when the tab gets focus)
|
||||
if (tab->_focusState != WUX::FocusState::Unfocused)
|
||||
{
|
||||
tab->ActivateBellIndicatorTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ namespace winrt::TerminalApp::implementation
|
|||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
winrt::fire_and_forget HideIcon(const bool hide);
|
||||
|
||||
winrt::fire_and_forget ShowBellIndicator(const bool show);
|
||||
winrt::fire_and_forget ActivateBellIndicatorTimer();
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
winrt::Microsoft::Terminal::Settings::Model::SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
|
||||
bool PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
|
@ -103,6 +106,9 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
|
||||
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
|
||||
void _MakeTabViewItem();
|
||||
|
||||
winrt::fire_and_forget _UpdateHeaderControlMaxWidth();
|
||||
|
|
Loading…
Reference in a new issue