diff --git a/src/Emulation.h b/src/Emulation.h index a416f25d3..bebf88b3a 100644 --- a/src/Emulation.h +++ b/src/Emulation.h @@ -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 diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index 9c61a7098..a1c9544cb 100644 --- a/src/Vt102Emulation.cpp +++ b/src/Vt102Emulation.cpp @@ -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()) { diff --git a/src/profile/Profile.cpp b/src/profile/Profile.cpp index df80c84d9..979324f7b 100644 --- a/src/profile/Profile.cpp +++ b/src/profile/Profile.cpp @@ -137,6 +137,7 @@ const std::vector 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}, diff --git a/src/profile/Profile.h b/src/profile/Profile.h index d89a262b5..930ddad49 100644 --- a/src/profile/Profile.h +++ b/src/profile/Profile.h @@ -18,8 +18,8 @@ #include // Konsole -#include "konsoleprivate_export.h" #include "Enumeration.h" +#include "konsoleprivate_export.h" #include #include @@ -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(Profile::BlinkingCursorEnabled); } + /** Convenience method for property(Profile::AnimatingCursorEnabled) */ + bool animatingCursorEnabled() const + { + return property(Profile::AnimatingCursorEnabled); + } /** Convenience method for property(Profile::FlowControlEnabled) */ bool flowControlEnabled() const diff --git a/src/terminalDisplay/TerminalDisplay.cpp b/src/terminalDisplay/TerminalDisplay.cpp index c80e5710e..bf129a64f 100644 --- a/src/terminalDisplay/TerminalDisplay.cpp +++ b/src/terminalDisplay/TerminalDisplay.cpp @@ -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(Profile::TripleClickMode)); setAutoCopySelectedText(profile->autoCopySelectedText()); diff --git a/src/terminalDisplay/TerminalDisplay.h b/src/terminalDisplay/TerminalDisplay.h index 4725730fc..eeb0d576c 100644 --- a/src/terminalDisplay/TerminalDisplay.h +++ b/src/terminalDisplay/TerminalDisplay.h @@ -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; }; } diff --git a/src/terminalDisplay/TerminalPainter.cpp b/src/terminalDisplay/TerminalPainter.cpp index e8074c287..d321ec421 100644 --- a/src/terminalDisplay/TerminalPainter.cpp +++ b/src/terminalDisplay/TerminalPainter.cpp @@ -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(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(); + 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); diff --git a/src/terminalDisplay/TerminalPainter.h b/src/terminalDisplay/TerminalPainter.h index 55159dc32..8c7600179 100644 --- a/src/terminalDisplay/TerminalPainter.h +++ b/src/terminalDisplay/TerminalPainter.h @@ -10,6 +10,9 @@ #define TERMINALPAINTER_HPP // Qt +#include +#include +#include #include // 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; }; } diff --git a/src/widgets/EditProfileAppearancePage.ui b/src/widgets/EditProfileAppearancePage.ui index 724db5dc0..09bedc747 100644 --- a/src/widgets/EditProfileAppearancePage.ui +++ b/src/widgets/EditProfileAppearancePage.ui @@ -451,6 +451,32 @@ + + + + Animation: + + + enableAnimatingCursorButton + + + + + + + + 0 + 0 + + + + Make the cursor animate smoothly + + + Enabled + + + diff --git a/src/widgets/EditProfileDialog.cpp b/src/widgets/EditProfileDialog.cpp index 849dd88a3..2f1d03e2d 100644 --- a/src/widgets/EditProfileDialog.cpp +++ b/src/widgets/EditProfileDialog.cpp @@ -875,6 +875,8 @@ void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile) // cursor options _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property(Profile::BlinkingCursorEnabled)); connect(_appearanceUi->enableBlinkingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor); + _appearanceUi->enableAnimatingCursorButton->setChecked(profile->property(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); diff --git a/src/widgets/EditProfileDialog.h b/src/widgets/EditProfileDialog.h index 59614cc0c..48bda68fe 100644 --- a/src/widgets/EditProfileDialog.h +++ b/src/widgets/EditProfileDialog.h @@ -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();