Compare commits

...

17 commits

Author SHA1 Message Date
Pankaj Bhojwani c45091372e conflict 2021-09-02 13:08:23 -07:00
Pankaj Bhojwani ad532c91ac Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/cursor_light 2021-08-25 10:02:09 -07:00
Pankaj Bhojwani 8c293d8f60 some comments, change name 2021-08-17 15:01:58 -07:00
Pankaj Bhojwani bd876fda85 move cursor with output/scroll 2021-08-17 14:22:44 -07:00
Pankaj Bhojwani 8c183b4125 Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/cursor_light 2021-08-16 10:39:26 -07:00
Pankaj Bhojwani 08e012aa6c initialize to macro 2021-08-10 13:02:18 -07:00
Pankaj Bhojwani ce8288f1b1 Schema 2021-08-09 11:52:39 -07:00
Pankaj Bhojwani 0be9b2afec comments 2021-08-09 11:24:00 -07:00
Pankaj Bhojwani b0e9bf33f2 check if cursor off screen 2021-08-09 11:08:02 -07:00
Pankaj Bhojwani ce2832c755 Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/cursor_light 2021-08-05 13:51:50 -07:00
Pankaj Bhojwani 02dad2a348 incomplete cursor offscreen 2021-08-04 13:45:40 -07:00
Pankaj Bhojwani cf6e1b4800 Reduce diff 2021-08-03 11:58:28 -07:00
Pankaj Bhojwani 2af96f18af accidental removal 2021-08-03 11:57:15 -07:00
Pankaj Bhojwani 3beeafa427 conflict 2021-08-03 11:53:41 -07:00
Pankaj Bhojwani 59cf2a6d4a minor 2021-07-29 10:57:19 -07:00
Pankaj Bhojwani e7d8fdb154 safer access light 2021-07-29 10:51:13 -07:00
Pankaj Bhojwani 58fd1b219c highlight cursor 2021-07-29 10:27:02 -07:00
17 changed files with 306 additions and 99 deletions

View file

@ -262,6 +262,7 @@
"findMatch",
"focusPane",
"globalSummon",
"highlightCursor",
"identifyWindow",
"identifyWindows",
"moveFocus",
@ -1026,6 +1027,17 @@
}
]
},
"HighlightCursorAction": {
"description": "The action to shine a spotlight on the current cursor location. If the cursor is off the screen, this action does nothing.",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "highlightCursor" }
}
}
]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@ -1062,6 +1074,7 @@
{ "$ref": "#/definitions/FocusPaneAction" },
{ "$ref": "#/definitions/GlobalSummonAction" },
{ "$ref": "#/definitions/QuakeModeAction" },
{ "$ref": "#/definitions/HighlightCursorAction" },
{ "type": "null" }
]
},

View file

@ -875,6 +875,16 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleHighlightCursor(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto termControl{ _GetActiveControl() })
{
termControl.HighlightCursor();
args.Handled(true);
}
}
void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View file

@ -1394,6 +1394,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _terminal != nullptr && _terminal->IsTrackingMouseInput();
}
bool ControlCore::IsCursorOffScreen() const
{
// If we haven't been initialized yet, then just return true
if (!_initializedTerminal)
{
return true;
}
auto lock = _terminal->LockForReading();
return _terminal->IsCursorOffScreen();
}
til::point ControlCore::CursorPosition() const
{
// If we haven't been initialized yet, then fake it.
@ -1499,6 +1511,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updatePatternLocations->Run();
}
void ControlCore::TrackCursorMovement(bool track) noexcept
{
_terminal->TrackCursorMovement(track);
}
// Method Description:
// - Clear the contents of the buffer. The region cleared is given by
// clearType:
@ -1557,5 +1574,4 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return hstring(ss.str());
}
}

View file

@ -70,6 +70,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double adjustment);
void ResumeRendering();
void TrackCursorMovement(bool track) noexcept;
void UpdatePatternLocations();
void SetHoveredCell(Core::Point terminalPosition);
void ClearHoveredCell();
@ -123,6 +125,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void CursorOn(const bool isCursorOn);
bool IsVtMouseModeEnabled() const;
bool IsCursorOffScreen() const;
til::point CursorPosition() const;
bool HasSelection() const;

View file

@ -78,6 +78,8 @@ namespace Microsoft.Terminal.Control
void SetBackgroundOpacity(Double opacity);
Microsoft.Terminal.Core.Color BackgroundColor { get; };
void TrackCursorMovement(Boolean track);
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
@ -89,6 +91,7 @@ namespace Microsoft.Terminal.Control
Boolean IsInReadOnlyMode { get; };
Boolean CursorOn;
void EnablePainting();
Boolean IsCursorOffScreen();
String ReadEntireBuffer();

View file

@ -118,6 +118,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
_moveCursorLight = std::make_shared<ThrottledFuncLeading>(
dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->_MoveCursorLightHelper();
}
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
dispatcher,
ScrollBarUpdateInterval,
@ -1677,6 +1687,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();
_updateScrollBar->Run(update);
if (CursorLight::GetIsTarget(RootGrid()))
{
_moveCursorLight->Run();
}
}
// Method Description:
@ -1697,6 +1711,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->TSFInputControl().TryRedrawCanvas();
if (CursorLight::GetIsTarget(RootGrid()))
{
_MoveCursorLightHelper();
}
}
}
@ -2402,6 +2420,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.TaskbarProgress();
}
// Method Description:
// - Toggles the spotlight on the cursor
// - If the cursor is currently off the screen, does nothing
void TermControl::HighlightCursor()
{
if (CursorLight::GetIsTarget(RootGrid()))
{
CursorLight::SetIsTarget(RootGrid(), false);
_core.TrackCursorMovement(false);
}
else if (_MoveCursorLightHelper())
{
CursorLight::SetIsTarget(RootGrid(), true);
_core.TrackCursorMovement(true);
}
}
// Method Description:
// - Computes the cursor position and moves the cursor light to it
// Return Value:
// - True if the cursor light was moved, false otherwise (this will
// happen if the cursor is off-screen)
bool TermControl::_MoveCursorLightHelper()
{
if (_core.IsCursorOffScreen())
{
// If the cursor is off screen, just switch off the light instead of moving it
CursorLight::SetIsTarget(RootGrid(), false);
return false;
}
else
{
// Compute the location of where to place the light
const auto charSizeInPixels = CharacterDimensions();
const auto htInDips = charSizeInPixels.Height / SwapChainPanel().CompositionScaleY();
const auto wtInDips = charSizeInPixels.Width / SwapChainPanel().CompositionScaleX();
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::size fontSize{ til::math::rounding, _core.FontSize() };
const til::point cursorPos = _core.CursorPosition();
const til::point cursorPosInPixels{ cursorPos * fontSize };
const til::point cursorPosInDIPs{ cursorPosInPixels / SwapChainPanel().CompositionScaleX() };
const til::point cursorLocationInDIPs{ cursorPosInDIPs + marginsInDips };
CursorLight().ChangeLocation(gsl::narrow_cast<float>(cursorLocationInDIPs.x()) + wtInDips / 2,
gsl::narrow_cast<float>(cursorLocationInDIPs.y()) + htInDips / 2);
return true;
}
}
void TermControl::BellLightOn()
{
// Initialize the animation if it does not exist

View file

@ -102,6 +102,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void BellLightOn();
void HighlightCursor();
bool ReadOnly() const noexcept;
void ToggleReadOnly();
@ -158,6 +160,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _initializedTerminal{ false };
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
std::shared_ptr<ThrottledFuncLeading> _moveCursorLight;
struct ScrollBarUpdate
{
@ -261,6 +264,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive);
void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
bool _MoveCursorLightHelper();
// TSFInputControl Handlers
void _CompositionCompleted(winrt::hstring text);
void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);

View file

@ -67,6 +67,8 @@ namespace Microsoft.Terminal.Control
void BellLightOn();
void HighlightCursor();
Boolean ReadOnly { get; };
void ToggleReadOnly();

View file

@ -35,6 +35,7 @@
<Grid x:Name="RootGrid">
<Grid.Lights>
<local:VisualBellLight x:Name="BellLight" />
<local:CursorLight x:Name="CursorLight" />
</Grid.Lights>
<Image x:Name="BackgroundImage"
AutomationProperties.AccessibilityView="Raw" />

View file

@ -5,6 +5,7 @@
#include "TermControl.h"
#include "XamlLights.h"
#include "VisualBellLight.g.cpp"
#include "CursorLight.g.cpp"
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Media;
@ -12,27 +13,7 @@ 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 } });
}
}
DependencyProperty CursorLight::_IsTargetProperty{ nullptr };
// Method Description:
// - This function is called when the first target UIElement is shown on the screen,
@ -49,59 +30,50 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
// 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 */)
void CursorLight::ChangeLocation(float xCoord, float yCoord)
{
if (CompositionLight())
if (const auto light = CompositionLight())
{
CompositionLight(nullptr);
}
}
winrt::hstring VisualBellLight::GetId()
{
return VisualBellLight::GetIdStatic();
}
void VisualBellLight::OnIsTargetChanged(DependencyObject const& d, DependencyPropertyChangedEventArgs const& e)
{
const auto uielem{ d.try_as<UIElement>() };
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();
if (isAdding)
{
if (uielem)
if (const auto cursorLight = light.try_as<Windows::UI::Composition::SpotLight>())
{
XamlLight::AddTargetElement(id, uielem);
}
else
{
XamlLight::AddTargetBrush(id, brush);
cursorLight.Offset({ xCoord, yCoord, 100 });
}
}
else
{
if (uielem)
{
XamlLight::RemoveTargetElement(id, uielem);
}
else
{
XamlLight::RemoveTargetBrush(id, brush);
}
_InitializeHelper(xCoord, yCoord);
}
}
// 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 CursorLight::OnConnected(UIElement const& /* newElement */)
{
if (!CompositionLight())
{
_InitializeHelper(0, 0);
}
}
// Method Description:
// - Helper to initialize the properties of the spotlight such as the location and
// the angles of the inner and outer cones
// Arguments:
// - xCoord: the x-coordinate of where to put the light
// - yCoord: the y-coordinate of where to put the light
void CursorLight::_InitializeHelper(float xCoord, float yCoord)
{
if (!CompositionLight())
{
auto spotLight{ Window::Current().Compositor().CreateSpotLight() };
spotLight.InnerConeColor(Windows::UI::Colors::White());
spotLight.InnerConeAngleInDegrees(10);
spotLight.OuterConeAngleInDegrees(25);
spotLight.Offset({ xCoord, yCoord, 100 });
CompositionLight(spotLight);
}
}
}

View file

@ -5,45 +5,123 @@
#include "cppwinrt_utils.h"
#include "VisualBellLight.g.h"
#include "CursorLight.g.h"
#define CREATE_XAML_LIGHT(name) \
public: \
name() \
{ \
_InitializeProperties(); \
} \
\
winrt::hstring GetId() \
{ \
return name::GetIdStatic(); \
} \
\
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 OnDisconnected(Windows::UI::Xaml::UIElement const& /*oldElement*/) \
{ \
if (CompositionLight()) \
{ \
CompositionLight(nullptr); \
} \
} \
\
static void OnIsTargetChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e) \
{ \
const auto uielem{ d.try_as<Windows::UI::Xaml::UIElement>() }; \
const auto brush{ d.try_as<Windows::UI::Xaml::Media::Brush>() }; \
\
if (!uielem && !brush) \
{ \
/* terminate early*/ \
return; \
} \
\
const auto isAdding = winrt::unbox_value<bool>(e.NewValue()); \
const auto id = GetIdStatic(); \
\
if (isAdding) \
{ \
if (uielem) \
{ \
Windows::UI::Xaml::Media::XamlLight::AddTargetElement(id, uielem); \
} \
else \
{ \
Windows::UI::Xaml::Media::XamlLight::AddTargetBrush(id, brush); \
} \
} \
else \
{ \
if (uielem) \
{ \
Windows::UI::Xaml::Media::XamlLight::RemoveTargetElement(id, uielem); \
} \
else \
{ \
Windows::UI::Xaml::Media::XamlLight::RemoveTargetBrush(id, brush); \
} \
} \
} \
\
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::name>().Name; \
} \
\
private: \
static Windows::UI::Xaml::DependencyProperty _IsTargetProperty; \
static void _InitializeProperties() \
{ \
if (!_IsTargetProperty) \
{ \
_IsTargetProperty = \
Windows::UI::Xaml::DependencyProperty::RegisterAttached( \
L"IsTarget", \
winrt::xaml_typename<bool>(), \
winrt::xaml_typename<Control::name>(), \
Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(false), Windows::UI::Xaml::PropertyChangedCallback{ &name::OnIsTargetChanged } }); \
} \
}
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);
CREATE_XAML_LIGHT(VisualBellLight);
};
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;
}
struct CursorLight : CursorLightT<CursorLight>
{
void ChangeLocation(float xCoord, float yCoord);
void OnConnected(Windows::UI::Xaml::UIElement const& newElement);
CREATE_XAML_LIGHT(CursorLight);
private:
static void _InitializeProperties();
static Windows::UI::Xaml::DependencyProperty _IsTargetProperty;
void _InitializeHelper(float xCoord, float yCoord);
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(VisualBellLight);
BASIC_FACTORY(CursorLight);
}

View file

@ -1,13 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#define XAML_LIGHT(Name) \
Name(); \
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)
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);
XAML_LIGHT(VisualBellLight);
}
[default_interface] runtimeclass CursorLight : Windows.UI.Xaml.Media.XamlLight
{
XAML_LIGHT(CursorLight);
void ChangeLocation(Single xCoord, Single yCoord);
}
}

View file

@ -1078,12 +1078,17 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
// Firing the CursorPositionChanged event is very expensive so we try not to do that when
// the cursor does not need to be redrawn.
if (!cursor.IsDeferDrawing())
if (!cursor.IsDeferDrawing() || _trackingCursorMovement)
{
_NotifyTerminalCursorPositionChanged();
}
}
void Terminal::TrackCursorMovement(bool track) noexcept
{
_trackingCursorMovement = track;
}
void Terminal::UserScrollViewport(const int viewTop)
{
// we're going to modify state here that the renderer could be reading.
@ -1220,6 +1225,17 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
return cursor.IsBlinkingAllowed();
}
// Method Description:
// - Computes whether the cursor is currently off the screen
// Return value:
// - True if the cursor if off the screen, false otherwise
bool Terminal::IsCursorOffScreen() noexcept
{
const auto absoluteCursorPosY = _buffer->GetCursor().GetPosition().Y;
const auto scrollOffset = GetScrollOffset();
return absoluteCursorPosY < scrollOffset || absoluteCursorPosY >= (scrollOffset + _GetMutableViewport().Height());
}
// Method Description:
// - Update our internal knowledge about where regex patterns are on the screen
// - This is called by TerminalControl (through a throttled function) when the visible

View file

@ -213,10 +213,13 @@ public:
void SetCursorOn(const bool isOn);
bool IsCursorBlinkingAllowed() const noexcept;
bool IsCursorOffScreen() noexcept;
void UpdatePatternsUnderLock() noexcept;
void ClearPatternTree() noexcept;
void TrackCursorMovement(bool track) noexcept;
const std::optional<til::color> GetTabColor() const noexcept;
til::color GetDefaultBackground() const noexcept;
@ -289,6 +292,8 @@ private:
size_t _hyperlinkPatternId;
bool _trackingCursorMovement{ false };
std::wstring _workingDirectory;
// This default fake font value is only used to check if the font is a raster font.

View file

@ -65,6 +65,7 @@ static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
static constexpr std::string_view GlobalSummonKey{ "globalSummon" };
static constexpr std::string_view QuakeModeKey{ "quakeMode" };
static constexpr std::string_view FocusPaneKey{ "focusPane" };
static constexpr std::string_view HighlightCursorKey{ "highlightCursor" };
static constexpr std::string_view ClearBufferKey{ "clearBuffer" };
static constexpr std::string_view MultipleActionsKey{ "multipleActions" };
@ -368,6 +369,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },
{ ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::HighlightCursor, RS_(L"HighlightCursorCommandKey") },
{ ShortcutAction::ClearBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName
};

View file

@ -79,6 +79,7 @@
ON_ALL_ACTIONS(GlobalSummon) \
ON_ALL_ACTIONS(QuakeMode) \
ON_ALL_ACTIONS(FocusPane) \
ON_ALL_ACTIONS(HighlightCursor) \
ON_ALL_ACTIONS(ClearBuffer) \
ON_ALL_ACTIONS(MultipleActions)

View file

@ -178,6 +178,9 @@
<data name="CloseWindowCommandKey" xml:space="preserve">
<value>Close window</value>
</data>
<data name="HighlightCursorCommandKey" xml:space="preserve">
<value>Highlight cursor</value>
</data>
<data name="CommandPromptDisplayName" xml:space="preserve">
<value>Command Prompt</value>
<comment>This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt"</comment>