Add animation cursor feature

This commit is contained in:
The Infinitys
2025-12-14 00:11:19 +09:00
parent 7cd37ab8d5
commit b84fdda993
11 changed files with 197 additions and 15 deletions

View File

@@ -409,9 +409,11 @@ Q_SIGNALS:
* to the terminal.
* @p shape cursor shape
* @p isBlinking if true, the cursor will be set to blink
* @p isAnimating if true, the cursor will be set to animate
* @p customColor custom cursor color
*/
void setCursorStyleRequest(Enum::CursorShapeEnum shape = Enum::BlockCursor, bool isBlinking = false, const QColor &customColor = {});
void
setCursorStyleRequest(Enum::CursorShapeEnum shape = Enum::BlockCursor, bool isBlinking = false, bool isAnimating = false, const QColor &customColor = {});
/**
* Emitted when reset() is called to reset the cursor style to the

View File

@@ -1520,6 +1520,7 @@ void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const u
if (attribute == Session::ProfileChange) {
bool styleChanged = false;
bool isBlinking = false;
bool isAnimating = false;
Enum::CursorShapeEnum shape = Enum::BlockCursor;
QColor customColor;
@@ -1536,6 +1537,7 @@ void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const u
const QLatin1String cursorShapeStr("CursorShape=");
const QLatin1String blinkingCursorStr("BlinkingCursorEnabled=");
const QLatin1String animatingCursorStr("AnimatingCursorEnabled=");
const QLatin1String customCursorColorStr("CustomCursorColor=");
QString newValue;
@@ -1562,7 +1564,22 @@ void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const u
styleChanged = true;
}
}
} else if (item.startsWith(animatingCursorStr)) {
const auto str = item.mid(animatingCursorStr.size());
if (str.compare(QString::fromLatin1("true"), Qt::CaseInsensitive) == 0) {
isAnimating = true;
styleChanged = true;
} else if (str.compare(QString::fromLatin1("false"), Qt::CaseInsensitive) == 0) {
isAnimating = false;
styleChanged = true;
} else {
bool ok = false;
bool newIsAnimating = str.toInt(&ok);
if (ok) {
isAnimating = newIsAnimating;
styleChanged = true;
}
}
} else if (item.startsWith(customCursorColorStr)) {
const auto colorStr = item.mid(customCursorColorStr.size());
customColor = QColor(colorStr);
@@ -1576,7 +1593,7 @@ void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const u
}
if (styleChanged) {
Q_EMIT setCursorStyleRequest(shape, isBlinking, customColor);
Q_EMIT setCursorStyleRequest(shape, isBlinking, isAnimating, customColor);
}
if (newValue.isEmpty()) {

View File

@@ -137,6 +137,7 @@ const std::vector<Profile::PropertyInfo> Profile::DefaultProperties = {
{BidiLineLTR, "BidiLineLTR", TERMINAL_GROUP, true},
{BidiTableDirOverride, "BidiTableDirOverride", TERMINAL_GROUP, true},
{BlinkingCursorEnabled, "BlinkingCursorEnabled", TERMINAL_GROUP, false},
{AnimatingCursorEnabled, "AnimatingCursorEnabled", TERMINAL_GROUP, false},
{BellMode, "BellMode", TERMINAL_GROUP, Enum::NotifyBell},
{VerticalLine, "VerticalLine", TERMINAL_GROUP, false},
{VerticalLineAtChar, "VerticalLineAtChar", TERMINAL_GROUP, 80},

View File

@@ -18,8 +18,8 @@
#include <QVariant>
// Konsole
#include "konsoleprivate_export.h"
#include "Enumeration.h"
#include "konsoleprivate_export.h"
#include <map>
#include <vector>
@@ -187,6 +187,9 @@ public:
* to text editing applications )
*/
BlinkingCursorEnabled,
/** (bool) Specifies whether the cursor animates
*/
AnimatingCursorEnabled,
/** (bool) If true, terminal displays use a fixed color to draw the
* cursor, specified by the CustomCursorColor property. Otherwise
* the cursor changes color to match the character underneath it.
@@ -728,6 +731,11 @@ public:
{
return property<bool>(Profile::BlinkingCursorEnabled);
}
/** Convenience method for property<bool>(Profile::AnimatingCursorEnabled) */
bool animatingCursorEnabled() const
{
return property<bool>(Profile::AnimatingCursorEnabled);
}
/** Convenience method for property<bool>(Profile::FlowControlEnabled) */
bool flowControlEnabled() const

View File

@@ -326,6 +326,18 @@ TerminalDisplay::TerminalDisplay(QWidget *parent)
_printManager.reset(new KonsolePrintManager(ldrawBackground, ldrawContents, lgetBackgroundColor));
ubidi = ubidi_open();
animationTimer = new QTimer(this);
connect(animationTimer, &QTimer::timeout, this, [this]() {
qreal step = 0.2;
qreal newX = currentCursorRect.x() + (targetCursorRect.x() - currentCursorRect.x()) * step;
qreal newY = currentCursorRect.y() + (targetCursorRect.y() - currentCursorRect.y()) * step;
currentCursorRect.moveTo(newX, newY);
if (qAbs(currentCursorRect.x() - targetCursorRect.x()) < 0.1 && qAbs(currentCursorRect.y() - targetCursorRect.y()) < 0.1) { }
update();
});
}
TerminalDisplay::~TerminalDisplay()
@@ -378,12 +390,13 @@ void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
_cursorShape = shape;
}
void TerminalDisplay::setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking, const QColor &customColor)
void TerminalDisplay::setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking, bool isAnimating, const QColor &customColor)
{
setKeyboardCursorShape(shape);
setBlinkingCursorEnabled(isBlinking);
setAnimatingCursorEnabled(isAnimating);
if (customColor.isValid()) {
_terminalColor->setCursorColor(customColor);
}
@@ -409,6 +422,7 @@ void TerminalDisplay::resetCursorStyle()
setKeyboardCursorShape(shape);
setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled());
setAnimatingCursorEnabled(currentProfile->animatingCursorEnabled());
}
}
@@ -851,6 +865,12 @@ void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
}
}
void TerminalDisplay::setAnimatingCursorEnabled(bool animate)
{
_allowAnimatingCursor = animate;
_cursorAnimating = animate;
}
void TerminalDisplay::setBlinkingTextEnabled(bool blink)
{
_allowBlinkingText = blink;
@@ -3126,6 +3146,7 @@ void TerminalDisplay::applyProfile(const Profile::Ptr &profile)
// terminal features
setBlinkingCursorEnabled(profile->blinkingCursorEnabled());
setAnimatingCursorEnabled(profile->animatingCursorEnabled());
setBlinkingTextEnabled(profile->blinkingTextEnabled());
_tripleClickMode = Enum::TripleClickModeEnum(profile->property<int>(Profile::TripleClickMode));
setAutoCopySelectedText(profile->autoCopySelectedText());

View File

@@ -138,6 +138,9 @@ public:
/** Specifies whether or not the cursor can blink. */
void setBlinkingCursorEnabled(bool blink);
/** Specifies whether or not the cursor can animate. */
void setAnimatingCursorEnabled(bool animate);
/** Specifies whether or not text can blink. */
void setBlinkingTextEnabled(bool blink);
@@ -161,9 +164,10 @@ public:
* Sets the Cursor Style (DECSCUSR) via escape sequences
* @p shape cursor shape
* @p isBlinking if true, the cursor will be set to blink
* @p isAnimating if true, the cursor will be set to animate
* @p customColor custom cursor color
*/
void setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking, const QColor &customColor);
void setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking, bool isAnimating, const QColor &customColor);
/**
* Resets the cursor style to the current profile cursor shape and
@@ -328,6 +332,10 @@ public:
{
return _cursorBlinking;
}
bool cursorAnimating() const
{
return _cursorAnimating;
}
bool textBlinking() const
{
@@ -730,8 +738,10 @@ private:
bool _allowBlinkingText = true; // allow text to blink
bool _allowBlinkingCursor = false; // allow cursor to blink
bool _allowAnimatingCursor = false; // allow cursor to animate
bool _textBlinking = false; // text is blinking, hide it when drawing
bool _cursorBlinking = false; // cursor is blinking, hide it when drawing
bool _cursorAnimating = false; // cursor is animating, animate it when drawing
bool _hasTextBlinker = false; // has characters to blink
QTimer *_blinkTextTimer = nullptr;
QTimer *_blinkCursorTimer = nullptr;
@@ -833,6 +843,9 @@ private:
int _id;
static int lastViewId;
QRectF currentCursorRect;
QRectF targetCursorRect;
QTimer *animationTimer;
};
}

View File

@@ -40,10 +40,69 @@ const QChar LTR_OVERRIDE_CHAR(0x202D);
namespace Konsole
{
QVariant interpolatePolygonF(const QPolygonF &start, const QPolygonF &end, qreal progress)
{
if (start.size() != end.size())
return end;
QPolygonF result;
for (int i = 0; i < start.size(); ++i) {
QPointF p = start[i] + (end[i] - start[i]) * progress;
result << p;
}
return QVariant::fromValue(result);
}
TerminalPainter::TerminalPainter(TerminalDisplay *parent)
: QObject(parent)
, m_parentDisplay(parent)
{
qRegisterAnimationInterpolator<QPolygonF>(interpolatePolygonF);
m_cursorAnim = new QVariantAnimation(this);
m_cursorAnim->setDuration(200);
m_cursorAnim->setEasingCurve(QEasingCurve::OutCubic);
connect(m_cursorAnim, &QVariantAnimation::valueChanged, this, &TerminalPainter::updateCursorAnimation);
}
void TerminalPainter::updateCursorAnimation(const QVariant &value)
{
m_animatedCursorPolygon = value.value<QPolygonF>();
m_parentDisplay->update();
}
QPolygonF createDynamicPolygon(const QRectF &rect, qreal shear, qreal taper)
{
QPolygonF poly;
qreal dx = -shear * rect.height();
qreal topWidthReduction = (taper < 0) ? (rect.width() * -taper) : 0;
qreal bottomWidthReduction = (taper > 0) ? (rect.width() * taper) : 0;
QPointF topLeft(rect.left() + dx + topWidthReduction, rect.top());
QPointF topRight(rect.right() + dx - topWidthReduction, rect.top());
QPointF bottomRight(rect.right() - bottomWidthReduction, rect.bottom());
QPointF bottomLeft(rect.left() + bottomWidthReduction, rect.bottom());
poly << topLeft << topRight << bottomRight << bottomLeft;
return poly;
}
void TerminalPainter::onCursorPositionChanged(const QRectF &oldRect, const QRectF &newRect)
{
m_cursorAnim->stop();
qreal deltaX = oldRect.x() - newRect.x();
qreal deltaY = oldRect.y() - newRect.y();
qreal targetShear = qBound(-0.25, -deltaX * 0.015, 0.25);
qreal targetTaper = qBound(-0.3, deltaY * 0.02, 0.3);
QPolygonF startPoly = createDynamicPolygon(oldRect, targetShear, targetTaper);
QPolygonF endPoly = createDynamicPolygon(newRect, 0.0, 0.0);
m_cursorAnim->setStartValue(QVariant::fromValue(startPoly));
m_cursorAnim->setEndValue(QVariant::fromValue(endPoly));
m_cursorAnim->start();
}
static inline bool isLineCharString(const QString &string, bool braille)
@@ -587,23 +646,41 @@ void TerminalPainter::updateCursorTextColor(const QColor &backgroundColor, QColo
void TerminalPainter::drawCursor(QPainter &painter, const QRectF &cursorRect, const QColor &foregroundColor, const QColor &backgroundColor, QColor &characterColor)
{
if (m_parentDisplay->cursorBlinking()) {
return;
const qreal width = qMax(m_parentDisplay->terminalFont()->fontWidth() / 12.0, 1.0);
const qreal halfWidth = width / 2.0;
if (m_lastTargetRect != cursorRect) {
QRectF nextRect;
if (m_parentDisplay->cursorShape() == Enum::UnderlineCursor) {
nextRect = QRectF(cursorRect.left(), cursorRect.bottom() - width, cursorRect.width(), width);
} else if (m_parentDisplay->cursorShape() == Enum::IBeamCursor) {
nextRect = QRectF(cursorRect.left(), cursorRect.top(), width, cursorRect.height());
} else {
nextRect = cursorRect;
}
onCursorPositionChanged(m_lastTargetRect, nextRect);
m_lastTargetRect = cursorRect;
}
QColor color = m_parentDisplay->terminalColor()->cursorColor();
QColor cursorColor = color.isValid() ? color : foregroundColor;
if (m_parentDisplay->cursorAnimating() && m_cursorAnim->state() == QAbstractAnimation::Running && !m_animatedCursorPolygon.isEmpty()) {
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setBrush(cursorColor);
painter.setPen(Qt::NoPen);
painter.drawPolygon(m_animatedCursorPolygon, Qt::OddEvenFill);
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setBrush(Qt::NoBrush);
}
if (m_parentDisplay->cursorBlinking()) {
return;
}
QPen pen(cursorColor);
pen.setJoinStyle(Qt::MiterJoin);
// TODO: the relative pen width to draw the cursor is a bit hacky
// and set to 1/12 of the font width. Visually it seems to work at
// all scales but there must be better ways to do it
const qreal width = qMax(m_parentDisplay->terminalFont()->fontWidth() / 12.0, 1.0);
const qreal halfWidth = width / 2.0;
pen.setWidthF(width);
pen.setJoinStyle(Qt::MiterJoin);
painter.setPen(pen);
if (m_parentDisplay->cursorShape() == Enum::BlockCursor) {
if (m_parentDisplay->hasFocus()) {
painter.fillRect(cursorRect, cursorColor);

View File

@@ -10,6 +10,9 @@
#define TERMINALPAINTER_HPP
// Qt
#include <QPolygonF>
#include <QRectF>
#include <QVariantAnimation>
#include <QVector>
// Konsole
@@ -132,6 +135,11 @@ private:
QColor oldColor,
QFont::Weight normalWeight,
QFont::Weight boldWeight);
void updateCursorAnimation(const QVariant &value);
void onCursorPositionChanged(const QRectF &oldRect, const QRectF &newRect);
QVariantAnimation *m_cursorAnim;
QRectF m_lastTargetRect;
QPolygonF m_animatedCursorPolygon;
};
}

View File

@@ -451,6 +451,32 @@
</property>
</widget>
</item>
<item row="9" column="0" alignment="Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignVCenter">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Animation:</string>
</property>
<property name="buddy">
<cstring>enableAnimatingCursorButton</cstring>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="enableAnimatingCursorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Make the cursor animate smoothly</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">

View File

@@ -875,6 +875,8 @@ void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
// cursor options
_appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
connect(_appearanceUi->enableBlinkingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
_appearanceUi->enableAnimatingCursorButton->setChecked(profile->property<bool>(Profile::AnimatingCursorEnabled));
connect(_appearanceUi->enableAnimatingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleAnimatingCursor);
if (profile->useCustomCursorColor()) {
_appearanceUi->customCursorColorButton->setChecked(true);
@@ -1032,6 +1034,12 @@ void EditProfileDialog::toggleBlinkingCursor(bool enable)
updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable);
}
void EditProfileDialog::toggleAnimatingCursor(bool enable)
{
preview(Profile::AnimatingCursorEnabled, enable);
updateTempProfileProperty(Profile::AnimatingCursorEnabled, enable);
}
void EditProfileDialog::setCursorShape(int index)
{
preview(Profile::CursorShape, index);

View File

@@ -148,6 +148,7 @@ private Q_SLOTS:
// void focusBorderColor();
void focusBorderColorChanged(const QColor &color);
void toggleBlinkingCursor(bool);
void toggleAnimatingCursor(bool);
void setCursorShape(int);
void autoCursorColor();
void customCursorColor();