mirror of
https://github.com/KDE/konsole.git
synced 2026-04-28 17:59:25 -04:00
Add animation cursor feature
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user