-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a setting to flash the pane when BEL is emitted #9270
Changes from 30 commits
370e0c0
3d7c9db
54a683d
b4ae30c
d59a432
c1172cd
e0c9f89
e89f273
f93c9e8
4c125c3
4b19b57
76c182a
5ed7e93
678cfb5
cecc6d8
23dc1fd
9282a16
eedb317
5b72726
692ad14
8a42804
b3c50f8
ab0ce5f
8026698
fb27787
2f9bc61
084ff79
f1237d9
b675b2a
6c9d8ef
730cf09
f5bb54e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
_lastAutoScrollUpdateTime{ std::nullopt }, | ||
_cursorTimer{}, | ||
_blinkTimer{}, | ||
_searchBox{ nullptr } | ||
_searchBox{ nullptr }, | ||
_bellLightAnimation{ Window::Current().Compositor().CreateScalarKeyFrameAnimation() } | ||
{ | ||
InitializeComponent(); | ||
|
||
|
@@ -167,6 +168,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
_autoScrollTimer.Interval(AutoScrollUpdateInterval); | ||
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll }); | ||
|
||
// Add key frames and a duration to our bell light animation | ||
_bellLightAnimation.InsertKeyFrame(0.0, 2.0); | ||
_bellLightAnimation.InsertKeyFrame(1.0, 1.0); | ||
_bellLightAnimation.Duration(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(TerminalWarningBellInterval))); | ||
|
||
_ApplyUISettings(_settings); | ||
} | ||
|
||
|
@@ -2358,6 +2364,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
return _core->TaskbarProgress(); | ||
} | ||
|
||
void TermControl::BellLightOn() | ||
{ | ||
Windows::Foundation::Numerics::float2 zeroSize{ 0, 0 }; | ||
// If the grid has 0 size or if the bell timer is | ||
// already active, do nothing | ||
if (RootGrid().ActualSize() != zeroSize && !_bellLightTimer) | ||
{ | ||
// Start the timer, when the timer ticks we switch off the light | ||
DispatcherTimer invertTimer; | ||
invertTimer.Interval(std::chrono::milliseconds(TerminalWarningBellInterval)); | ||
invertTimer.Tick({ get_weak(), &TermControl::_BellLightOff }); | ||
invertTimer.Start(); | ||
_bellLightTimer.emplace(std::move(invertTimer)); | ||
PankajBhojwani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Switch on the light and animate the intensity to fade out | ||
VisualBellLight::SetIsTarget(RootGrid(), true); | ||
BellLight().CompositionLight().StartAnimation(L"Intensity", _bellLightAnimation); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to be clear, the animation runs from Intensity=2.0->1.0, then is switched off entirely at the end of the time. That's neat |
||
} | ||
} | ||
|
||
void TermControl::_BellLightOff(Windows::Foundation::IInspectable const& /* sender */, | ||
Windows::Foundation::IInspectable const& /* e */) | ||
{ | ||
if (_bellLightTimer) | ||
{ | ||
// Stop the timer and switch off the light | ||
_bellLightTimer->Stop(); | ||
_bellLightTimer.reset(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Theoretically, once the timer's been initialized, we don't need to keep creating and destroying it each time. We could just re-use it, but I'm not gonna block over that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good notes for cleanup later. 😄 |
||
|
||
if (!_closing) | ||
{ | ||
VisualBellLight::SetIsTarget(RootGrid(), false); | ||
} | ||
} | ||
} | ||
|
||
// Method Description: | ||
// - Checks whether the control is in a read-only mode (in this mode node input is sent to connection). | ||
// Return Value: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
#pragma once | ||
|
||
#include "TermControl.g.h" | ||
#include "XamlLights.h" | ||
#include "EventArgs.h" | ||
#include "../../renderer/base/Renderer.hpp" | ||
#include "../../renderer/dx/DxRenderer.hpp" | ||
|
@@ -98,6 +99,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
const winrt::hstring& padding, | ||
const uint32_t dpi); | ||
|
||
void BellLightOn(); | ||
|
||
bool ReadOnly() const noexcept; | ||
void ToggleReadOnly(); | ||
|
||
|
@@ -164,8 +167,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer; | ||
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime; | ||
|
||
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. meh that seems fine to me. In the future, we could always init this as winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr }; and only ctor it when actually requested in the settings, but again, meh |
||
|
||
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer; | ||
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer; | ||
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellLightTimer; | ||
|
||
event_token _coreOutputEventToken; | ||
|
||
|
@@ -202,6 +208,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
|
||
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); | ||
void _BlinkTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); | ||
void _BellLightOff(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); | ||
|
||
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
#include "pch.h" | ||
#include "TermControl.h" | ||
#include "XamlLights.h" | ||
#include "VisualBellLight.g.cpp" | ||
|
||
using namespace winrt::Windows::UI::Xaml; | ||
using namespace winrt::Windows::UI::Xaml::Media; | ||
|
||
namespace winrt::Microsoft::Terminal::Control::implementation | ||
{ | ||
DependencyProperty VisualBellLight::_IsTargetProperty{ nullptr }; | ||
|
||
VisualBellLight::VisualBellLight() | ||
{ | ||
_InitializeProperties(); | ||
} | ||
|
||
void VisualBellLight::_InitializeProperties() | ||
{ | ||
// Initialize any dependency properties here. | ||
// This performs a lazy load on these properties, instead of | ||
// initializing them when the DLL loads. | ||
if (!_IsTargetProperty) | ||
{ | ||
_IsTargetProperty = | ||
DependencyProperty::RegisterAttached( | ||
L"IsTarget", | ||
winrt::xaml_typename<bool>(), | ||
winrt::xaml_typename<Control::VisualBellLight>(), | ||
PropertyMetadata{ winrt::box_value(false), PropertyChangedCallback{ &VisualBellLight::OnIsTargetChanged } }); | ||
} | ||
} | ||
|
||
// Method Description: | ||
// - This function is called when the first target UIElement is shown on the screen, | ||
// this enables delaying composition object creation until it's actually necessary. | ||
// Arguments: | ||
// - newElement: unused | ||
void VisualBellLight::OnConnected(UIElement const& /* newElement */) | ||
{ | ||
if (!CompositionLight()) | ||
{ | ||
auto spotLight{ Window::Current().Compositor().CreateAmbientLight() }; | ||
spotLight.Color(Windows::UI::Colors::White()); | ||
CompositionLight(spotLight); | ||
} | ||
} | ||
|
||
// Method Description: | ||
// - This function is called when there are no more target UIElements on the screen | ||
// - Disposes of composition resources when no longer in use | ||
// Arguments: | ||
// - oldElement: unused | ||
void VisualBellLight::OnDisconnected(UIElement const& /* oldElement */) | ||
{ | ||
if (CompositionLight()) | ||
{ | ||
CompositionLight(nullptr); | ||
} | ||
} | ||
|
||
winrt::hstring VisualBellLight::GetId() | ||
{ | ||
return VisualBellLight::GetIdStatic(); | ||
} | ||
|
||
void VisualBellLight::OnIsTargetChanged(DependencyObject const& d, DependencyPropertyChangedEventArgs const& e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I totally understand what's going on with this method. This is a static function that's called when Or is this just forward thinking? All this seems like handy boilerplate around There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So yeah I think this is some cool stuff -- he's created an "attached property" (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep! I've mostly just adopted the code from the xaml light docs. Lights can only target a |
||
{ | ||
const auto uielem{ d.try_as<UIElement>() }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kinda surprised that |
||
const auto brush{ d.try_as<Brush>() }; | ||
|
||
if (!uielem && !brush) | ||
{ | ||
// terminate early | ||
return; | ||
} | ||
|
||
const auto isAdding = winrt::unbox_value<bool>(e.NewValue()); | ||
const auto id = GetIdStatic(); | ||
|
||
isAdding ? (uielem ? XamlLight::AddTargetElement(id, uielem) : XamlLight::AddTargetBrush(id, brush)) : | ||
PankajBhojwani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(uielem ? XamlLight::RemoveTargetElement(id, uielem) : XamlLight::RemoveTargetBrush(id, brush)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
#pragma once | ||
|
||
#include "cppwinrt_utils.h" | ||
#include "VisualBellLight.g.h" | ||
|
||
namespace winrt::Microsoft::Terminal::Control::implementation | ||
{ | ||
struct VisualBellLight : VisualBellLightT<VisualBellLight> | ||
{ | ||
VisualBellLight(); | ||
|
||
winrt::hstring GetId(); | ||
|
||
static Windows::UI::Xaml::DependencyProperty IsTargetProperty() { return _IsTargetProperty; } | ||
|
||
static bool GetIsTarget(Windows::UI::Xaml::DependencyObject const& target) | ||
{ | ||
return winrt::unbox_value<bool>(target.GetValue(_IsTargetProperty)); | ||
} | ||
|
||
static void SetIsTarget(Windows::UI::Xaml::DependencyObject const& target, bool value) | ||
{ | ||
target.SetValue(_IsTargetProperty, winrt::box_value(value)); | ||
} | ||
|
||
void OnConnected(Windows::UI::Xaml::UIElement const& newElement); | ||
void OnDisconnected(Windows::UI::Xaml::UIElement const& oldElement); | ||
|
||
static void OnIsTargetChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e); | ||
|
||
inline static winrt::hstring GetIdStatic() | ||
{ | ||
// This specifies the unique name of the light. In most cases you should use the type's full name. | ||
return winrt::xaml_typename<winrt::Microsoft::Terminal::Control::VisualBellLight>().Name; | ||
} | ||
|
||
private: | ||
static void _InitializeProperties(); | ||
static Windows::UI::Xaml::DependencyProperty _IsTargetProperty; | ||
}; | ||
} | ||
|
||
namespace winrt::Microsoft::Terminal::Control::factory_implementation | ||
{ | ||
BASIC_FACTORY(VisualBellLight); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
namespace Microsoft.Terminal.Control | ||
{ | ||
[default_interface] runtimeclass VisualBellLight : Windows.UI.Xaml.Media.XamlLight | ||
{ | ||
VisualBellLight(); | ||
static Windows.UI.Xaml.DependencyProperty IsTargetProperty { get; }; | ||
static Boolean GetIsTarget(Windows.UI.Xaml.DependencyObject target); | ||
static void SetIsTarget(Windows.UI.Xaml.DependencyObject target, Boolean value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -436,7 +436,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation | |
INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(BackgroundImageStretchMode, BackgroundImageStretchMode, winrt::Windows::UI::Xaml::Media::Stretch, L"Profile_BackgroundImageStretchMode", L"Content"); | ||
INITIALIZE_BINDABLE_ENUM_SETTING(AntiAliasingMode, TextAntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode, L"Profile_AntialiasingMode", L"Content"); | ||
INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); | ||
INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(BellStyle, BellStyle, winrt::Microsoft::Terminal::Settings::Model::BellStyle, L"Profile_BellStyle", L"Content"); | ||
INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); | ||
|
||
// manually add Custom FontWeight option. Don't add it to the Map | ||
|
@@ -573,7 +572,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation | |
} | ||
else if (settingName == L"BellStyle") | ||
{ | ||
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentBellStyle" }); | ||
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"IsBellStyleFlagSet" }); | ||
} | ||
else if (settingName == L"ScrollState") | ||
{ | ||
|
@@ -627,6 +626,32 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation | |
_State.Profile().ColorSchemeName(val.Name()); | ||
} | ||
|
||
bool Profiles::IsBellStyleFlagSet(const uint32_t flag) | ||
{ | ||
return (WI_EnumValue(_State.Profile().BellStyle()) & flag) == flag; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait uh, what exactly is happening here? Is this just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explained offline -- this is because the WI macros require a compile-time constant. We have to do it manually. |
||
} | ||
|
||
void Profiles::SetBellStyleAudible(winrt::Windows::Foundation::IReference<bool> on) | ||
{ | ||
auto currentStyle = State().Profile().BellStyle(); | ||
WI_UpdateFlag(currentStyle, Model::BellStyle::Audible, winrt::unbox_value<bool>(on)); | ||
State().Profile().BellStyle(currentStyle); | ||
} | ||
|
||
void Profiles::SetBellStyleWindow(winrt::Windows::Foundation::IReference<bool> on) | ||
{ | ||
auto currentStyle = State().Profile().BellStyle(); | ||
WI_UpdateFlag(currentStyle, Model::BellStyle::Window, winrt::unbox_value<bool>(on)); | ||
State().Profile().BellStyle(currentStyle); | ||
} | ||
|
||
void Profiles::SetBellStyleTaskbar(winrt::Windows::Foundation::IReference<bool> on) | ||
{ | ||
auto currentStyle = State().Profile().BellStyle(); | ||
WI_UpdateFlag(currentStyle, Model::BellStyle::Taskbar, winrt::unbox_value<bool>(on)); | ||
State().Profile().BellStyle(currentStyle); | ||
} | ||
|
||
void Profiles::DeleteConfirmation_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) | ||
{ | ||
auto state{ winrt::get_self<ProfilePageNavigationState>(_State) }; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like the wrong abstraction-- the app should not tell the control to blink after the control tells the app that there was a bell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your comment about the visual bell moving up into the application resonates with me. Thank you.