terminal/src/renderer/base/BlinkingState.cpp
James Holderness d1671a0acd
Add support for the "blink" graphic rendition attribute (#7490)
This PR adds support for the _blink_ graphic rendition attribute. When a
character is output with this attribute set, it "blinks" at a regular
interval, by cycling its color between the normal rendition and a dimmer
shade of that color.

The majority of the blinking mechanism is encapsulated in a new
`BlinkingState` class, which is shared between the Terminal and Conhost
implementations. This class keeps track of the position in the blinking
cycle, which determines whether characters are rendered as normal or
faint. 

In Windows Terminal, the state is stored in the `Terminal` class, and in
Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases,
the `IsBlinkingFaint` method is used to determine the current blinking
rendition, and that is passed on as a parameter to the
`TextAttribute::CalculateRgbColors` method when these classes are
looking up attribute colors.

Prior to calculating the colors, the current attribute is also passed to
the `RecordBlinkingUsage` method, which keeps track of whether there are
actually any blink attributes in use. This is used to determine whether
the screen needs to be refreshed when the blinking cycle toggles between
the normal and faint renditions.

The refresh itself is handled by the `ToggleBlinkingRendition` method,
which is triggered by a timer. In Conhost this is just piggybacking on
the existing cursor blink timer, but in Windows Terminal it needs to
have its own separate timer, since the cursor timer is reset whenever a
key is pressed, which is not something we want for attribute blinking.

Although the `ToggleBlinkingRendition` is called at the same rate as the
cursor blinking, we actually only want the cells to blink at half that
frequency. We thus have a counter that cycles through four phases, and
blinking is rendered as faint for two of those four. Then every two
cycles - when the state changes - a redraw is triggered, but only if
there are actually blinking attributes in use (as previously recorded).

As mentioned earlier, the blinking frequency is based on the cursor
blink rate, so that means it'll automatically be disabled if a user has
set their cursor blink rate to none. It can also be disabled by turning
off the _Show animations in Windows_ option. In Conhost these settings
take effect immediately, but in Windows Terminal they only apply when a
new tab is opened.

This PR also adds partial support for the `SGR 6` _rapid blink_
attribute. This is not used by DEC terminals, but was defined in the
ECMA/ANSI standards. It's not widely supported, but many terminals just
it implement it as an alias for the regular `SGR 5` blink attribute, so
that's what I've done here too.

## Validation Steps Performed

I've checked the _Graphic rendition test pattern_ in Vttest, and
compared our representation of the blink attribute to that of an actual
DEC VT220 terminal as seen on [YouTube]. With the right color scheme
it's a reasonably close match.

[YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s

Closes #7388
2020-09-21 23:21:33 +00:00

82 lines
2.8 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../inc/BlinkingState.hpp"
using namespace Microsoft::Console::Render;
// Method Description:
// - Updates the flag indicating whether cells with the blinking attribute
// can animate on and off.
// Arguments:
// - blinkingAllowed: true if blinking is permitted.
// Return Value:
// - <none>
void BlinkingState::SetBlinkingAllowed(const bool blinkingAllowed) noexcept
{
_blinkingAllowed = blinkingAllowed;
if (!_blinkingAllowed)
{
_blinkingShouldBeFaint = false;
}
}
// Method Description:
// - Makes a record of whether the given attribute has blinking enabled or
// not, so we can determine whether the screen will need to be refreshed
// when the blinking rendition state changes.
// Arguments:
// - attr: a text attribute that is expected to be rendered.
// Return Value:
// - <none>
void BlinkingState::RecordBlinkingUsage(const TextAttribute& attr) noexcept
{
_blinkingIsInUse = _blinkingIsInUse || attr.IsBlinking();
}
// Method Description:
// - Determines whether cells with the blinking attribute should be rendered
// as normal or faint, based on the current position in the blinking cycle.
// Arguments:
// - <none>
// Return Value:
// - True if blinking cells should be rendered as faint.
bool BlinkingState::IsBlinkingFaint() const noexcept
{
return _blinkingShouldBeFaint;
}
// Method Description:
// - Increments the position in the blinking cycle, toggling the blinking
// rendition state on every second call, potentially triggering a redraw of
// the given render target if there are blinking cells currently in view.
// Arguments:
// - renderTarget: the render target that will be redrawn.
// Return Value:
// - <none>
void BlinkingState::ToggleBlinkingRendition(IRenderTarget& renderTarget) noexcept
try
{
if (_blinkingAllowed)
{
// This method is called with the frequency of the cursor blink rate,
// but we only want our cells to blink at half that frequency. We thus
// have a blinking cycle that loops through four phases...
_blinkingCycle = (_blinkingCycle + 1) % 4;
// ... and two of those four render the blinking attributes as faint.
_blinkingShouldBeFaint = _blinkingCycle >= 2;
// Every two cycles (when the state changes), we need to trigger a
// redraw, but only if there are actually blinking attributes in use.
if (_blinkingIsInUse && _blinkingCycle % 2 == 0)
{
// We reset the _blinkingIsInUse flag before redrawing, so we can
// get a fresh assessment of the current blinking attribute usage.
_blinkingIsInUse = false;
renderTarget.TriggerRedrawAll();
}
}
}
CATCH_LOG()