Files
z-bar-qt/Plugins/ZShell/Blobs/blobrect2.cpp

354 lines
10 KiB
C++

#include "blobrect2.hpp"
#include "blobgroup2.hpp"
#include <algorithm>
#include <cmath>
namespace {
constexpr float k_retireMs = 120.0f;
} // namespace
BlobRect2::BlobRect2(QQuickItem* parent) : BlobShape2(parent) {}
BlobRect2::~BlobRect2() {
if (m_group) m_group->removeShape(this);
}
void BlobRect2::finishAtIdentity() {
m_dm00 = 1.0f;
m_dm01 = 0.0f;
m_dm11 = 1.0f;
m_deformMatrix = QMatrix4x4();
m_retiring = false;
m_physicsActive = false;
emit rawDeformMatrixChanged();
updateCenteredDeformMatrix();
}
void BlobRect2::beginRetire() {
// The settle check would otherwise re-enter this every frame and restart the
// curve timer, so the curve never reaches t = 1.
if (m_retiring) return;
m_retire00 = m_dm00;
m_retire01 = m_dm01;
m_retire11 = m_dm11;
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
m_retireElapsed.start();
m_retiring = true;
}
void BlobRect2::updatePhysics() {
// Shapes are data only, so the group drives physics here.
if (m_retiring) {
const float t = std::clamp(
static_cast<float>(m_retireElapsed.elapsed()) / k_retireMs,
0.0f,
1.0f);
const float s = t * t * (3.0f - 2.0f * t);
if (t >= 1.0f) {
finishAtIdentity();
return;
}
m_dm00 = m_retire00 + (1.0f - m_retire00) * s;
m_dm01 = m_retire01 - m_retire01 * s;
m_dm11 = m_retire11 + (1.0f - m_retire11) * s;
m_deformMatrix = QMatrix4x4(
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
emit rawDeformMatrixChanged();
updateCenteredDeformMatrix();
if (!m_physicsActive) return;
}
// No "not active" guard here: an inactive shape still has to be evaluated so it
// can see the panel start moving and become active. The frame loop is only kept
// alive while active, and it is requested before anything can return early —
// requesting it at the end lets a shape stop being evaluated with the matrix
// still off identity, which is what left the panel contents blurry.
if (m_physicsActive && m_group) m_group->markDirty();
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
if (!m_hasPrevPos) {
m_prevScenePos = scenePos;
m_elapsed.start();
m_hasPrevPos = true;
return;
}
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
if (dt > 0.1f || dt < 0.001f) {
m_prevScenePos = scenePos;
if (m_physicsActive) checkAtRest(0.0f);
return;
}
const float velX =
static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
const float velY =
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
m_prevScenePos = scenePos;
const float speed = std::sqrt(velX * velX + velY * velY);
if (m_retiring) {
if (speed > 5.0f)
m_retiring = false;
else
return;
}
if (!m_physicsActive) {
if (speed < 5.0f) return;
m_physicsActive = true;
}
const float kStretchFactor = static_cast<float>(m_deformScale);
constexpr float kMaxStretch = 0.35f;
float target00 = 1.0f;
float target01 = 0.0f;
float target11 = 1.0f;
if (speed > 5.0f) {
const float targetStretch =
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
const float targetCompress = 1.0f / targetStretch;
const float cosA = velX / speed;
const float sinA = velY / speed;
const float cos2 = cosA * cosA;
const float sin2 = sinA * sinA;
const float cs = cosA * sinA;
target00 = targetStretch * cos2 + targetCompress * sin2;
target01 = (targetStretch - targetCompress) * cs;
target11 = targetStretch * sin2 + targetCompress * cos2;
}
const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping);
const float invDamp = 1.0f / (1.0f + kDamping * dt);
m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp;
m_dm00 += m_dmVel00 * dt;
m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp;
m_dm01 += m_dmVel01 * dt;
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
m_dm11 += m_dmVel11 * dt;
m_deformMatrix = QMatrix4x4(
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
emit rawDeformMatrixChanged();
updateCenteredDeformMatrix();
// Start the retire as soon as the matrix is close to identity, as in v1. Waiting
// for every term to fall below kEpsilon keeps the matrix just off identity for
// longer, and the panel content is transformed by it, which reads as blur on
// text.
const float totalDelta =
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
const float totalVel =
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
// The residual is already inside the rest epsilon, so the eased curve would only
// be aborted or restarted every frame by the panel creeping. Snap instead.
if (totalDelta < 0.004f && totalVel < 0.05f) {
finishAtIdentity();
return;
}
checkAtRest(speed);
}
void BlobRect2::setTopLeftRadius(qreal r) {
if (!qFuzzyCompare(m_topLeftRadius, r)) {
m_topLeftRadius = r;
emit topLeftRadiusChanged();
if (m_group) m_group->markDirty();
}
}
void BlobRect2::setTopRightRadius(qreal r) {
if (!qFuzzyCompare(m_topRightRadius, r)) {
m_topRightRadius = r;
emit topRightRadiusChanged();
if (m_group) m_group->markDirty();
}
}
void BlobRect2::setBottomLeftRadius(qreal r) {
if (!qFuzzyCompare(m_bottomLeftRadius, r)) {
m_bottomLeftRadius = r;
emit bottomLeftRadiusChanged();
if (m_group) m_group->markDirty();
}
}
void BlobRect2::setBottomRightRadius(qreal r) {
if (!qFuzzyCompare(m_bottomRightRadius, r)) {
m_bottomRightRadius = r;
emit bottomRightRadiusChanged();
if (m_group) m_group->markDirty();
}
}
void BlobRect2::cornerRadii(float out[4]) const {
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
const auto base = std::min(static_cast<float>(m_radius), maxR);
out[0] = std::min(
m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base,
maxR);
out[1] = std::min(
m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius)
: base,
maxR);
out[2] = std::min(
m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base,
maxR);
out[3] = std::min(
m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base,
maxR);
}
bool BlobRect2::isExcluded(const BlobShape2* other) const {
for (const auto& ptr : m_exclude) {
if (ptr == other) return true;
}
return false;
}
bool BlobRect2::isCornerExcluded(const BlobShape2* other) const {
for (const auto& ptr : m_excludeCorners) {
if (ptr == other) return true;
}
return false;
}
QQmlListProperty<BlobRect2> BlobRect2::exclude() {
return QQmlListProperty<BlobRect2>(
this,
nullptr,
&excludeAppend,
&excludeCount,
&excludeAt,
&excludeClear,
&excludeReplace,
&excludeRemoveLast);
}
QQmlListProperty<BlobRect2> BlobRect2::excludeCorners() {
return QQmlListProperty<BlobRect2>(
this,
nullptr,
&excludeCornersAppend,
&excludeCornersCount,
&excludeCornersAt,
&excludeCornersClear,
&excludeCornersReplace,
&excludeCornersRemoveLast);
}
void BlobRect2::excludeAppend(
QQmlListProperty<BlobRect2>* prop, BlobRect2* rect) {
auto* self = static_cast<BlobRect2*>(prop->object);
self->m_exclude.append(rect);
if (self->m_group) self->m_group->markDirty();
emit self->excludeChanged();
}
qsizetype BlobRect2::excludeCount(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
return self->m_exclude.size();
}
BlobRect2* BlobRect2::excludeAt(
QQmlListProperty<BlobRect2>* prop, qsizetype index) {
auto* self = static_cast<BlobRect2*>(prop->object);
return self->m_exclude.at(index);
}
void BlobRect2::excludeClear(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
if (self->m_exclude.isEmpty()) return;
self->m_exclude.clear();
if (self->m_group) self->m_group->markDirty();
emit self->excludeChanged();
}
void BlobRect2::excludeReplace(
QQmlListProperty<BlobRect2>* prop, qsizetype index, BlobRect2* rect) {
auto* self = static_cast<BlobRect2*>(prop->object);
self->m_exclude[index] = rect;
if (self->m_group) self->m_group->markDirty();
emit self->excludeChanged();
}
void BlobRect2::excludeRemoveLast(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
if (self->m_exclude.isEmpty()) return;
self->m_exclude.removeLast();
if (self->m_group) self->m_group->markDirty();
emit self->excludeChanged();
}
void BlobRect2::excludeCornersAppend(
QQmlListProperty<BlobRect2>* prop, BlobRect2* rect) {
auto* self = static_cast<BlobRect2*>(prop->object);
self->m_excludeCorners.append(rect);
if (self->m_group) self->m_group->markDirty();
emit self->excludeCornersChanged();
}
qsizetype BlobRect2::excludeCornersCount(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
return self->m_excludeCorners.size();
}
BlobRect2* BlobRect2::excludeCornersAt(
QQmlListProperty<BlobRect2>* prop, qsizetype index) {
auto* self = static_cast<BlobRect2*>(prop->object);
return self->m_excludeCorners.at(index);
}
void BlobRect2::excludeCornersClear(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
if (self->m_excludeCorners.isEmpty()) return;
self->m_excludeCorners.clear();
if (self->m_group) self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect2::excludeCornersReplace(
QQmlListProperty<BlobRect2>* prop, qsizetype index, BlobRect2* rect) {
auto* self = static_cast<BlobRect2*>(prop->object);
self->m_excludeCorners[index] = rect;
if (self->m_group) self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect2::excludeCornersRemoveLast(QQmlListProperty<BlobRect2>* prop) {
auto* self = static_cast<BlobRect2*>(prop->object);
if (self->m_excludeCorners.isEmpty()) return;
self->m_excludeCorners.removeLast();
if (self->m_group) self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect2::checkAtRest(float speed) {
if (m_retiring) return;
constexpr float kEpsilon = 0.002f;
const bool atRest =
std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
speed < 5.0f;
if (atRest) beginRetire();
}