terminal/src/buffer/out/cursor.cpp
James Holderness bb71179a24
Consolidate the color palette APIs (#11784)
This PR merges the default colors and cursor color into the main color
table, enabling us to simplify the `ConGetSet` and `ITerminalApi`
interfaces, with just two methods required for getting and setting any
form of color palette entry.

The is a follow-up to the color table standardization in #11602, and a
another small step towards de-duplicating `AdaptDispatch` and
`TerminalDispatch` for issue #3849. It should also make it easier to
support color queries (#3718) and a configurable bold color (#5682) in
the future.

On the conhost side, default colors could originally be either indexed
positions in the 16-color table, or separate standalone RGB values. With
the new system, the default colors will always be in the color table, so
we just need to track their index positions.

To make this work, those positions need to be calculated at startup
based on the loaded registry/shortcut settings, and updated when
settings are changed (this is handled in
`CalculateDefaultColorIndices`). But the plus side is that it's now much
easier to lookup the default color values for rendering.

For now the default colors in Windows Terminal use hardcoded positions,
because it doesn't need indexed default colors like conhost. But in the
future I'd like to extend the index handling to both terminals, so we
can eventually support the VT525 indexed color operations.

As for the cursor color, that was previously stored in the `Cursor`
class, which meant that it needed to be copied around in various places
where cursors were being instantiated. Now that it's managed separately
in the color table, a lot of that code is no longer required.

## Validation
Some of the unit test initialization code needed to be updated to setup
the color table and default index values as required for the new system.
There were also some adjustments needed to account for API changes, in
particular for methods that now take index values for the default colors
in place of COLORREFs. But for the most part, the essential behavior of
the tests remains unchanged.

I've also run a variety of manual tests looking at the legacy console
APIs as well as the various VT color sequences, and checking that
everything works as expected when color schemes are changed, both in
Windows Terminal and conhost, and in the latter case with both indexed
colors and RGB values.

Closes #11768
2021-11-23 18:28:55 +00:00

338 lines
8.1 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "cursor.h"
#include "TextBuffer.hpp"
#pragma hdrstop
// Routine Description:
// - Constructor to set default properties for Cursor
// Arguments:
// - ulSize - The height of the cursor within this buffer
Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept :
_parentBuffer{ parentBuffer },
_cPosition{ 0 },
_fHasMoved(false),
_fIsVisible(true),
_fIsOn(true),
_fIsDouble(false),
_fBlinkingAllowed(true),
_fDelay(false),
_fIsConversionArea(false),
_fIsPopupShown(false),
_fDelayedEolWrap(false),
_coordDelayedAt{ 0 },
_fDeferCursorRedraw(false),
_fHaveDeferredCursorRedraw(false),
_ulSize(ulSize),
_cursorType(CursorType::Legacy)
{
}
Cursor::~Cursor()
{
}
COORD Cursor::GetPosition() const noexcept
{
return _cPosition;
}
bool Cursor::HasMoved() const noexcept
{
return _fHasMoved;
}
bool Cursor::IsVisible() const noexcept
{
return _fIsVisible;
}
bool Cursor::IsOn() const noexcept
{
return _fIsOn;
}
bool Cursor::IsBlinkingAllowed() const noexcept
{
return _fBlinkingAllowed;
}
bool Cursor::IsDouble() const noexcept
{
return _fIsDouble;
}
bool Cursor::IsConversionArea() const noexcept
{
return _fIsConversionArea;
}
bool Cursor::IsPopupShown() const noexcept
{
return _fIsPopupShown;
}
bool Cursor::GetDelay() const noexcept
{
return _fDelay;
}
ULONG Cursor::GetSize() const noexcept
{
return _ulSize;
}
void Cursor::SetHasMoved(const bool fHasMoved) noexcept
{
_fHasMoved = fHasMoved;
}
void Cursor::SetIsVisible(const bool fIsVisible) noexcept
{
_fIsVisible = fIsVisible;
_RedrawCursor();
}
void Cursor::SetIsOn(const bool fIsOn) noexcept
{
_fIsOn = fIsOn;
_RedrawCursorAlways();
}
void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept
{
_fBlinkingAllowed = fBlinkingAllowed;
_RedrawCursorAlways();
}
void Cursor::SetIsDouble(const bool fIsDouble) noexcept
{
_fIsDouble = fIsDouble;
_RedrawCursor();
}
void Cursor::SetIsConversionArea(const bool fIsConversionArea) noexcept
{
// Functionally the same as "Hide cursor"
// Never called with TRUE, it's only used in the creation of a
// ConversionAreaInfo, and never changed after that.
_fIsConversionArea = fIsConversionArea;
_RedrawCursorAlways();
}
void Cursor::SetIsPopupShown(const bool fIsPopupShown) noexcept
{
// Functionally the same as "Hide cursor"
_fIsPopupShown = fIsPopupShown;
_RedrawCursorAlways();
}
void Cursor::SetDelay(const bool fDelay) noexcept
{
_fDelay = fDelay;
}
void Cursor::SetSize(const ULONG ulSize) noexcept
{
_ulSize = ulSize;
_RedrawCursor();
}
void Cursor::SetStyle(const ULONG ulSize, const CursorType type) noexcept
{
_ulSize = ulSize;
_cursorType = type;
_RedrawCursor();
}
// Routine Description:
// - Sends a redraw message to the renderer only if the cursor is currently on.
// - NOTE: For use with most methods in this class.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Cursor::_RedrawCursor() noexcept
{
// Only trigger the redraw if we're on.
// Don't draw the cursor if this was triggered from a conversion area.
// (Conversion areas have cursors to mark the insertion point internally, but the user's actual cursor is the one on the primary screen buffer.)
if (IsOn() && !IsConversionArea())
{
if (_fDeferCursorRedraw)
{
_fHaveDeferredCursorRedraw = true;
}
else
{
_RedrawCursorAlways();
}
}
}
// Routine Description:
// - Sends a redraw message to the renderer no matter what.
// - NOTE: For use with the method that turns the cursor on and off to force a refresh
// and clear the ON cursor from the screen. Not for use with other methods.
// They should use the other method so refreshes are suppressed while the cursor is off.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Cursor::_RedrawCursorAlways() noexcept
{
try
{
_parentBuffer.GetRenderTarget().TriggerRedrawCursor(&_cPosition);
}
CATCH_LOG();
}
void Cursor::SetPosition(const COORD cPosition) noexcept
{
_RedrawCursor();
_cPosition.X = cPosition.X;
_cPosition.Y = cPosition.Y;
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::SetXPosition(const int NewX) noexcept
{
_RedrawCursor();
_cPosition.X = gsl::narrow<SHORT>(NewX);
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::SetYPosition(const int NewY) noexcept
{
_RedrawCursor();
_cPosition.Y = gsl::narrow<SHORT>(NewY);
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::IncrementXPosition(const int DeltaX) noexcept
{
_RedrawCursor();
_cPosition.X += gsl::narrow<SHORT>(DeltaX);
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::IncrementYPosition(const int DeltaY) noexcept
{
_RedrawCursor();
_cPosition.Y += gsl::narrow<SHORT>(DeltaY);
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::DecrementXPosition(const int DeltaX) noexcept
{
_RedrawCursor();
_cPosition.X -= gsl::narrow<SHORT>(DeltaX);
_RedrawCursor();
ResetDelayEOLWrap();
}
void Cursor::DecrementYPosition(const int DeltaY) noexcept
{
_RedrawCursor();
_cPosition.Y -= gsl::narrow<SHORT>(DeltaY);
_RedrawCursor();
ResetDelayEOLWrap();
}
///////////////////////////////////////////////////////////////////////////////
// Routine Description:
// - Copies properties from another cursor into this one.
// - This is primarily to copy properties that would otherwise not be specified during CreateInstance
// - NOTE: As of now, this function is specifically used to handle the ResizeWithReflow operation.
// It will need modification for other future users.
// Arguments:
// - OtherCursor - The cursor to copy properties from
// Return Value:
// - <none>
void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept
{
// We shouldn't copy the position as it will be already rearranged by the resize operation.
//_cPosition = pOtherCursor->_cPosition;
_fHasMoved = OtherCursor._fHasMoved;
_fIsVisible = OtherCursor._fIsVisible;
_fIsOn = OtherCursor._fIsOn;
_fIsDouble = OtherCursor._fIsDouble;
_fBlinkingAllowed = OtherCursor._fBlinkingAllowed;
_fDelay = OtherCursor._fDelay;
_fIsConversionArea = OtherCursor._fIsConversionArea;
// A resize operation should invalidate the delayed end of line status, so do not copy.
//_fDelayedEolWrap = OtherCursor._fDelayedEolWrap;
//_coordDelayedAt = OtherCursor._coordDelayedAt;
_fDeferCursorRedraw = OtherCursor._fDeferCursorRedraw;
_fHaveDeferredCursorRedraw = OtherCursor._fHaveDeferredCursorRedraw;
// Size will be handled separately in the resize operation.
//_ulSize = OtherCursor._ulSize;
_cursorType = OtherCursor._cursorType;
}
void Cursor::DelayEOLWrap(const COORD coordDelayedAt) noexcept
{
_coordDelayedAt = coordDelayedAt;
_fDelayedEolWrap = true;
}
void Cursor::ResetDelayEOLWrap() noexcept
{
_coordDelayedAt = { 0 };
_fDelayedEolWrap = false;
}
COORD Cursor::GetDelayedAtPosition() const noexcept
{
return _coordDelayedAt;
}
bool Cursor::IsDelayedEOLWrap() const noexcept
{
return _fDelayedEolWrap;
}
void Cursor::StartDeferDrawing() noexcept
{
_fDeferCursorRedraw = true;
}
bool Cursor::IsDeferDrawing() noexcept
{
return _fDeferCursorRedraw;
}
void Cursor::EndDeferDrawing() noexcept
{
if (_fHaveDeferredCursorRedraw)
{
_RedrawCursorAlways();
}
_fDeferCursorRedraw = FALSE;
}
const CursorType Cursor::GetType() const noexcept
{
return _cursorType;
}
void Cursor::SetType(const CursorType type) noexcept
{
_cursorType = type;
}