fix: apply character bubble anchors
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"bubble": {
|
"bubble": {
|
||||||
"offsetX": 0,
|
"offsetX": 0,
|
||||||
"offsetY": -180
|
"offsetY": -475
|
||||||
},
|
},
|
||||||
"states": {
|
"states": {
|
||||||
"idle": {
|
"idle": {
|
||||||
|
|||||||
@@ -14,6 +14,21 @@ struct CharacterState
|
|||||||
QString nextState;
|
QString nextState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CharacterBase
|
||||||
|
{
|
||||||
|
int width = 320;
|
||||||
|
int height = 320;
|
||||||
|
double scale = 1.0;
|
||||||
|
double anchorX = 0.5;
|
||||||
|
double anchorY = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterBubble
|
||||||
|
{
|
||||||
|
double offsetX = 0.0;
|
||||||
|
double offsetY = -8.0;
|
||||||
|
};
|
||||||
|
|
||||||
class CharacterPackage
|
class CharacterPackage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -25,9 +40,10 @@ public:
|
|||||||
QString packagePath;
|
QString packagePath;
|
||||||
QString previewPath;
|
QString previewPath;
|
||||||
QString defaultState;
|
QString defaultState;
|
||||||
|
CharacterBase base;
|
||||||
|
CharacterBubble bubble;
|
||||||
QMap<QString, CharacterState> states;
|
QMap<QString, CharacterState> states;
|
||||||
|
|
||||||
bool hasState(const QString &stateName) const;
|
bool hasState(const QString &stateName) const;
|
||||||
const CharacterState *state(const QString &stateName) const;
|
const CharacterState *state(const QString &stateName) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,62 @@ QString requiredString(const QJsonObject &object, const QString &key)
|
|||||||
|
|
||||||
return value.toString().trimmed();
|
return value.toString().trimmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int optionalPositiveInt(const QJsonObject &object, const QString &key, int fallback)
|
||||||
|
{
|
||||||
|
const int value = object.value(key).toInt(fallback);
|
||||||
|
return value > 0 ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
double optionalPositiveDouble(const QJsonObject &object, const QString &key, double fallback)
|
||||||
|
{
|
||||||
|
const double value = object.value(key).toDouble(fallback);
|
||||||
|
return value > 0.0 ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
double optionalNormalizedDouble(const QJsonObject &object, const QString &key, double fallback)
|
||||||
|
{
|
||||||
|
const double value = object.value(key).toDouble(fallback);
|
||||||
|
return qBound(0.0, value, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double optionalDouble(const QJsonObject &object, const QString &key, double fallback)
|
||||||
|
{
|
||||||
|
return object.value(key).toDouble(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterBase parseBase(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
CharacterBase base;
|
||||||
|
const QJsonValue baseValue = root.value(QStringLiteral("base"));
|
||||||
|
if (!baseValue.isObject())
|
||||||
|
{
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject baseObject = baseValue.toObject();
|
||||||
|
base.width = optionalPositiveInt(baseObject, QStringLiteral("width"), base.width);
|
||||||
|
base.height = optionalPositiveInt(baseObject, QStringLiteral("height"), base.height);
|
||||||
|
base.scale = optionalPositiveDouble(baseObject, QStringLiteral("scale"), base.scale);
|
||||||
|
base.anchorX = optionalNormalizedDouble(baseObject, QStringLiteral("anchorX"), base.anchorX);
|
||||||
|
base.anchorY = optionalNormalizedDouble(baseObject, QStringLiteral("anchorY"), base.anchorY);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterBubble parseBubble(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
CharacterBubble bubble;
|
||||||
|
const QJsonValue bubbleValue = root.value(QStringLiteral("bubble"));
|
||||||
|
if (!bubbleValue.isObject())
|
||||||
|
{
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject bubbleObject = bubbleValue.toObject();
|
||||||
|
bubble.offsetX = optionalDouble(bubbleObject, QStringLiteral("offsetX"), bubble.offsetX);
|
||||||
|
bubble.offsetY = optionalDouble(bubbleObject, QStringLiteral("offsetY"), bubble.offsetY);
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CharacterPackage CharacterPackageLoader::load(const QString &packagePath, QString *errorMessage)
|
CharacterPackage CharacterPackageLoader::load(const QString &packagePath, QString *errorMessage)
|
||||||
@@ -69,6 +126,8 @@ CharacterPackage CharacterPackageLoader::load(const QString &packagePath, QStrin
|
|||||||
package.author = requiredString(root, QStringLiteral("author"));
|
package.author = requiredString(root, QStringLiteral("author"));
|
||||||
package.version = requiredString(root, QStringLiteral("version"));
|
package.version = requiredString(root, QStringLiteral("version"));
|
||||||
package.defaultState = requiredString(root, QStringLiteral("defaultState"));
|
package.defaultState = requiredString(root, QStringLiteral("defaultState"));
|
||||||
|
package.base = parseBase(root);
|
||||||
|
package.bubble = parseBubble(root);
|
||||||
|
|
||||||
if (package.id.isEmpty())
|
if (package.id.isEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ namespace
|
|||||||
constexpr int MinBubbleWidth = 120;
|
constexpr int MinBubbleWidth = 120;
|
||||||
constexpr int MaxBubbleWidth = 420;
|
constexpr int MaxBubbleWidth = 420;
|
||||||
constexpr int MaxBubbleHeight = 220;
|
constexpr int MaxBubbleHeight = 220;
|
||||||
constexpr int BubbleOffsetY = 8;
|
|
||||||
constexpr int BubblePaddingWidth = 28;
|
constexpr int BubblePaddingWidth = 28;
|
||||||
constexpr int BubblePaddingHeight = 24;
|
constexpr int BubblePaddingHeight = 24;
|
||||||
constexpr int BubbleWidthSafetyMargin = 12;
|
constexpr int BubbleWidthSafetyMargin = 12;
|
||||||
@@ -98,6 +97,7 @@ void ChatBubble::showMessage(const QString &message, const QPoint &anchorPositio
|
|||||||
setFixedSize(bubbleSize);
|
setFixedSize(bubbleSize);
|
||||||
updatePosition();
|
updatePosition();
|
||||||
show();
|
show();
|
||||||
|
raise();
|
||||||
|
|
||||||
m_textEdit->verticalScrollBar()->setValue(
|
m_textEdit->verticalScrollBar()->setValue(
|
||||||
scrollToBottom ? m_textEdit->verticalScrollBar()->maximum() : 0);
|
scrollToBottom ? m_textEdit->verticalScrollBar()->maximum() : 0);
|
||||||
@@ -223,5 +223,5 @@ QSize ChatBubble::preferredBubbleSize(const QString &message) const
|
|||||||
|
|
||||||
void ChatBubble::updatePosition()
|
void ChatBubble::updatePosition()
|
||||||
{
|
{
|
||||||
move(m_anchorPosition.x() - width() / 2, m_anchorPosition.y() - height() - BubbleOffsetY);
|
move(m_anchorPosition.x() - width() / 2, m_anchorPosition.y() - height());
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-5
@@ -21,6 +21,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QPointF>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
@@ -46,10 +47,20 @@ QString previewImagePath()
|
|||||||
constexpr int MaxUserMessageLength = 4000;
|
constexpr int MaxUserMessageLength = 4000;
|
||||||
constexpr int ChatInputLowerOffsetY = 48;
|
constexpr int ChatInputLowerOffsetY = 48;
|
||||||
constexpr int StreamBubbleUpdateIntervalMs = 80;
|
constexpr int StreamBubbleUpdateIntervalMs = 80;
|
||||||
constexpr int BaseAnimationTargetSize = 320;
|
constexpr int MinAnimationTargetSide = 32;
|
||||||
|
constexpr int MaxAnimationTargetSide = 2048;
|
||||||
constexpr int LowPowerFpsCap = 6;
|
constexpr int LowPowerFpsCap = 6;
|
||||||
constexpr int ChatFinishedReturnDelayMs = 1500;
|
constexpr int ChatFinishedReturnDelayMs = 1500;
|
||||||
|
|
||||||
|
int boundedAnimationTargetSide(double sideLength)
|
||||||
|
{
|
||||||
|
const double boundedSideLength = qBound(
|
||||||
|
static_cast<double>(MinAnimationTargetSide),
|
||||||
|
sideLength,
|
||||||
|
static_cast<double>(MaxAnimationTargetSide));
|
||||||
|
return qRound(boundedSideLength);
|
||||||
|
}
|
||||||
|
|
||||||
int evenBoundedHistoryLimit(int value, int minimum, int maximum)
|
int evenBoundedHistoryLimit(int value, int minimum, int maximum)
|
||||||
{
|
{
|
||||||
const int boundedValue = qBound(minimum, value, maximum);
|
const int boundedValue = qBound(minimum, value, maximum);
|
||||||
@@ -801,7 +812,17 @@ void PetWindow::updateBubblePosition()
|
|||||||
|
|
||||||
QPoint PetWindow::bubbleAnchorPosition() const
|
QPoint PetWindow::bubbleAnchorPosition() const
|
||||||
{
|
{
|
||||||
return frameGeometry().topLeft() + QPoint(width() / 2, 0);
|
const CharacterBase &base = m_characterPackage.base;
|
||||||
|
const CharacterBubble &bubble = m_characterPackage.bubble;
|
||||||
|
const double baseWidth = base.width > 0 ? static_cast<double>(base.width) : static_cast<double>(width());
|
||||||
|
const double baseHeight = base.height > 0 ? static_cast<double>(base.height) : static_cast<double>(height());
|
||||||
|
const double scaleX = baseWidth > 0.0 ? static_cast<double>(width()) / baseWidth : 1.0;
|
||||||
|
const double scaleY = baseHeight > 0.0 ? static_cast<double>(height()) / baseHeight : 1.0;
|
||||||
|
const QPointF localAnchor(
|
||||||
|
static_cast<double>(width()) * base.anchorX + bubble.offsetX * scaleX,
|
||||||
|
static_cast<double>(height()) * base.anchorY + bubble.offsetY * scaleY);
|
||||||
|
|
||||||
|
return frameGeometry().topLeft() + QPoint(qRound(localAnchor.x()), qRound(localAnchor.y()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PetWindow::playState(const QString &stateName, bool centerWindow)
|
void PetWindow::playState(const QString &stateName, bool centerWindow)
|
||||||
@@ -869,9 +890,11 @@ void PetWindow::playResolvedState(const QString &stateName, bool centerWindow)
|
|||||||
|
|
||||||
QSize PetWindow::animationTargetSize() const
|
QSize PetWindow::animationTargetSize() const
|
||||||
{
|
{
|
||||||
const int sideLength = qRound(BaseAnimationTargetSize * m_appConfig.scale);
|
const CharacterBase &base = m_characterPackage.base;
|
||||||
const int boundedSideLength = qBound(BaseAnimationTargetSize / 2, sideLength, BaseAnimationTargetSize * 2);
|
const double totalScale = base.scale * m_appConfig.scale;
|
||||||
return QSize(boundedSideLength, boundedSideLength);
|
return QSize(
|
||||||
|
boundedAnimationTargetSide(static_cast<double>(base.width) * totalScale),
|
||||||
|
boundedAnimationTargetSide(static_cast<double>(base.height) * totalScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
int PetWindow::effectiveAnimationFps(int fps) const
|
int PetWindow::effectiveAnimationFps(int fps) const
|
||||||
|
|||||||
Reference in New Issue
Block a user