Skip to content

Commit b0f5cf6

Browse files
fuddlesworthruvnet
andcommitted
fix(animations): comprehensive PR review fixes — safety, perf, DRY, a11y
Fixes 28 issues from PR #291 review: Bugs: spring velocity sign inversion, negative ring buffer index, velocity index not reset on drag start, unchecked TimingMode/duration/ styleParam from JSON, elastic easing division-by-zero. Edge cases: near-critical omegaD guard, clamped initialVelocity from JSON, spring duration cap 10s→5s, tree resolution cycle guard, resolvedProfile default fallback, dampingRatio≤0 guard, epsilon slider step alignment. DRY: resolvedProfileOrDefault() helper in WTA, AnimationSubPage.qml base component, preset filtering helpers, ConfigDefaults for spring defaults, QLatin1String consistency in shader registries and geometryutils. Style: per-window usesOpacity(), showStyleSelector:false on fade/ border/preview, Kirigami.Units for hardcoded pixels, Accessible.name on sliders, required properties on SpringPreview/CurveThumbnail. Co-Authored-By: claude-flow <[email protected]>
1 parent b4f587c commit b0f5cf6

27 files changed

Lines changed: 297 additions & 309 deletions

kwin-effect/dragtracker.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ void DragTracker::handleWindowStartMoveResize(KWin::EffectWindow* w)
3636

3737
m_draggedWindow = w;
3838
m_draggedWindowId = m_effect->getWindowId(w);
39+
m_velocitySampleIndex = 0;
3940
m_lastCursorPos = KWin::effects->cursorPos();
4041
m_dragMovedThrottle.start();
4142

@@ -104,7 +105,8 @@ void DragTracker::finishDrag(bool cancelled)
104105
const int lastIdx = (m_velocitySampleIndex - 1) % VelocitySampleCount;
105106
// Search backwards for a sample at least 5ms older to avoid noise
106107
for (int offset = 1; offset < qMin(m_velocitySampleIndex, VelocitySampleCount); ++offset) {
107-
const int prevIdx = (m_velocitySampleIndex - 1 - offset) % VelocitySampleCount;
108+
const int prevIdx = ((m_velocitySampleIndex - 1 - offset) % VelocitySampleCount + VelocitySampleCount)
109+
% VelocitySampleCount;
108110
const qint64 dtMs = m_velocitySamples[lastIdx].timestampMs - m_velocitySamples[prevIdx].timestampMs;
109111
if (dtMs >= 5) {
110112
const QPointF dp = m_velocitySamples[lastIdx].position - m_velocitySamples[prevIdx].position;

kwin-effect/plasmazoneseffect.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2728,7 +2728,7 @@ std::optional<AnimationParams> PlasmaZonesEffect::resolveAnimationFromCachedTree
27282728
// Build chain from root to eventName
27292729
QStringList chain;
27302730
QString current = eventName;
2731-
while (!current.isEmpty()) {
2731+
while (!current.isEmpty() && chain.size() < 10) {
27322732
chain.prepend(current);
27332733
current = parentMap.value(current);
27342734
}
@@ -3216,7 +3216,7 @@ void PlasmaZonesEffect::prePaintWindow(KWin::RenderView* view, KWin::EffectWindo
32163216
data.setTransformed();
32173217
// Mark as translucent if any active animation uses opacity (Slide, Popin, SlideFade)
32183218
// or if the window is redirected for custom shader rendering (shaders may modify alpha)
3219-
if (m_windowAnimator->hasOpacityAnimations() || m_redirectedWindows.contains(w))
3219+
if (m_windowAnimator->usesOpacity(w) || m_redirectedWindows.contains(w))
32203220
data.setTranslucent();
32213221
}
32223222

kwin-effect/windowanimator.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ qreal EasingCurve::evaluateBounceOut(qreal t, qreal amp, int n)
4343
// Structure: one half-parabola (approach: 0→1) followed by n full parabolic
4444
// arcs of decreasing height. Amplitude scales the bounce dip depths.
4545
constexpr qreal r = 0.5;
46-
static_assert(r != 1.0, "r == 1.0 causes division by zero in bounce calculation");
4746
n = qBound(1, n, 8);
4847

4948
// Compute base arc duration d so all arcs fit in [0, 1]:
@@ -269,16 +268,22 @@ qreal SpringAnimation::evaluate(qreal t) const
269268
if (zeta < 1.0) {
270269
// Underdamped: oscillates
271270
const qreal omegaD = omega * qSqrt(1.0 - zeta * zeta);
271+
// Guard near-critical damping where omegaD approaches zero — fall through
272+
// to the critically-damped formula to avoid division by near-zero.
273+
if (omegaD < 1e-6) {
274+
const qreal decay = qExp(-omega * t);
275+
return 1.0 - decay * (1.0 + (omega - initialVelocity) * t);
276+
}
272277
const qreal decay = qExp(-zeta * omega * t);
273278
const qreal v0 = initialVelocity;
274-
// x(t) = 1 - e^(-zeta*omega*t) * (cos(omegaD*t) + ((zeta*omega + v0) / omegaD) * sin(omegaD*t))
275-
// where v0 is initial velocity contribution (0 when starting from rest toward target)
276-
const qreal sinTerm = (zeta * omega + v0) / omegaD;
279+
// x(t) = 1 - e^(-zeta*omega*t) * (cos(omegaD*t) + ((zeta*omega - v0) / omegaD) * sin(omegaD*t))
280+
// Positive v0 (drag toward target) reduces the sin coefficient to arrive faster.
281+
const qreal sinTerm = (zeta * omega - v0) / omegaD;
277282
return 1.0 - decay * (qCos(omegaD * t) + sinTerm * qSin(omegaD * t));
278283
} else if (qFuzzyCompare(zeta, 1.0)) {
279284
// Critically damped: fastest without oscillation
280285
const qreal decay = qExp(-omega * t);
281-
return 1.0 - decay * (1.0 + (omega + initialVelocity) * t);
286+
return 1.0 - decay * (1.0 + (omega - initialVelocity) * t);
282287
} else {
283288
// Overdamped: no oscillation, slower
284289
const qreal disc = qSqrt(zeta * zeta - 1.0);
@@ -288,7 +293,7 @@ qreal SpringAnimation::evaluate(qreal t) const
288293
// critically damped formula to avoid division by near-zero (s2 - s1).
289294
if (qAbs(s2 - s1) < 1e-6) {
290295
const qreal decay = qExp(-omega * t);
291-
return 1.0 - decay * (1.0 + (omega + initialVelocity) * t);
296+
return 1.0 - decay * (1.0 + (omega - initialVelocity) * t);
292297
}
293298
const qreal c2 = (initialVelocity - s1) / (s2 - s1);
294299
const qreal c1 = 1.0 - c2;
@@ -316,7 +321,7 @@ qreal SpringAnimation::estimatedDuration() const
316321
// Forward scan: require 3 consecutive settled samples to avoid
317322
// false positives from underdamped springs briefly crossing epsilon.
318323
constexpr qreal dt = 0.005;
319-
constexpr qreal maxT = 10.0;
324+
constexpr qreal maxT = 5.0;
320325
constexpr int requiredConsecutive = 3;
321326
int consecutive = 0;
322327
for (qreal t = dt; t <= maxT; t += dt) {
@@ -357,7 +362,7 @@ AnimationParams AnimationParams::fromJson(const QJsonObject& obj)
357362
p.spring.dampingRatio = qBound(0.1, sp.value(QLatin1String("dampingRatio")).toDouble(1.0), 10.0);
358363
p.spring.stiffness = qBound(1.0, sp.value(QLatin1String("stiffness")).toDouble(800.0), 2000.0);
359364
p.spring.epsilon = qBound(0.00001, sp.value(QLatin1String("epsilon")).toDouble(0.0001), 0.1);
360-
p.spring.initialVelocity = sp.value(QLatin1String("initialVelocity")).toDouble(0.0);
365+
p.spring.initialVelocity = qBound(-10.0, sp.value(QLatin1String("initialVelocity")).toDouble(0.0), 10.0);
361366
}
362367

363368
p.style = animationStyleFromString(obj.value(QLatin1String("style")).toString());

kwin-effect/windowanimator.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,13 @@ class WindowAnimator : public QObject
329329
return m_opacityAnimationCount > 0;
330330
}
331331

332+
/// Whether a specific window's animation uses opacity (Slide, Popin, SlideFade)
333+
bool usesOpacity(KWin::EffectWindow* w) const
334+
{
335+
auto it = m_animations.find(w);
336+
return it != m_animations.end() && it->usesOpacity();
337+
}
338+
332339
void removeAnimation(KWin::EffectWindow* window);
333340
void clear();
334341

src/core/animationprofile.cpp

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ SpringParams SpringParams::fromJson(const QJsonObject& obj)
3737
{
3838
SpringParams sp;
3939
if (obj.contains(QLatin1String("dampingRatio")))
40-
sp.dampingRatio =
41-
qBound(ConfigDefaults::springDampingRatioMin(), obj[QLatin1String("dampingRatio")].toDouble(1.0),
42-
ConfigDefaults::springDampingRatioMax());
40+
sp.dampingRatio = qBound(ConfigDefaults::springDampingRatioMin(),
41+
obj[QLatin1String("dampingRatio")].toDouble(ConfigDefaults::springDampingRatio()),
42+
ConfigDefaults::springDampingRatioMax());
4343
if (obj.contains(QLatin1String("stiffness")))
44-
sp.stiffness = qBound(ConfigDefaults::springStiffnessMin(), obj[QLatin1String("stiffness")].toDouble(800.0),
44+
sp.stiffness = qBound(ConfigDefaults::springStiffnessMin(),
45+
obj[QLatin1String("stiffness")].toDouble(ConfigDefaults::springStiffness()),
4546
ConfigDefaults::springStiffnessMax());
4647
if (obj.contains(QLatin1String("epsilon")))
47-
sp.epsilon = qBound(ConfigDefaults::springEpsilonMin(), obj[QLatin1String("epsilon")].toDouble(0.0001),
48+
sp.epsilon = qBound(ConfigDefaults::springEpsilonMin(),
49+
obj[QLatin1String("epsilon")].toDouble(ConfigDefaults::springEpsilon()),
4850
ConfigDefaults::springEpsilonMax());
4951
return sp;
5052
}
@@ -111,18 +113,23 @@ AnimationProfile AnimationProfile::fromJson(const QJsonObject& obj)
111113
AnimationProfile p;
112114
if (obj.contains(QLatin1String("enabled")))
113115
p.enabled = obj[QLatin1String("enabled")].toBool();
114-
if (obj.contains(QLatin1String("timingMode")))
115-
p.timingMode = static_cast<TimingMode>(obj[QLatin1String("timingMode")].toInt());
116+
if (obj.contains(QLatin1String("timingMode"))) {
117+
const int tm = obj[QLatin1String("timingMode")].toInt();
118+
if (tm >= 0 && tm <= 1)
119+
p.timingMode = static_cast<TimingMode>(tm);
120+
}
116121
if (obj.contains(QLatin1String("duration")))
117-
p.duration = obj[QLatin1String("duration")].toInt();
122+
p.duration = qBound(0, obj[QLatin1String("duration")].toInt(), 10000);
118123
if (obj.contains(QLatin1String("easingCurve")))
119124
p.easingCurve = obj[QLatin1String("easingCurve")].toString();
120125
if (obj.contains(QLatin1String("spring")))
121126
p.spring = SpringParams::fromJson(obj[QLatin1String("spring")].toObject());
122127
if (obj.contains(QLatin1String("style")))
123128
p.style = animationStyleFromString(obj[QLatin1String("style")].toString());
124-
if (obj.contains(QLatin1String("styleParam")))
125-
p.styleParam = obj[QLatin1String("styleParam")].toDouble();
129+
if (obj.contains(QLatin1String("styleParam"))) {
130+
const double sp = obj[QLatin1String("styleParam")].toDouble();
131+
p.styleParam = std::isfinite(sp) ? qBound(0.0, sp, 10.0) : 0.0;
132+
}
126133
if (obj.contains(QLatin1String("shaderPath")))
127134
p.shaderPath = obj[QLatin1String("shaderPath")].toString();
128135
if (obj.contains(QLatin1String("shaderParams")))
@@ -258,6 +265,31 @@ AnimationProfile AnimationProfileTree::resolvedProfile(const QString& eventName)
258265
if (it != m_profiles.end())
259266
resolved.mergeFrom(*it);
260267
}
268+
269+
// If the user tree left fields unset (e.g. no global node at all),
270+
// fill remaining nullopt fields from the built-in default tree.
271+
if (!resolved.enabled.has_value()) {
272+
const auto& def = defaultTree().resolvedProfile(eventName);
273+
if (!resolved.enabled.has_value())
274+
resolved.enabled = def.enabled;
275+
if (!resolved.timingMode.has_value())
276+
resolved.timingMode = def.timingMode;
277+
if (!resolved.duration.has_value())
278+
resolved.duration = def.duration;
279+
if (!resolved.spring.has_value())
280+
resolved.spring = def.spring;
281+
if (!resolved.easingCurve.has_value())
282+
resolved.easingCurve = def.easingCurve;
283+
if (!resolved.style.has_value())
284+
resolved.style = def.style;
285+
if (!resolved.styleParam.has_value())
286+
resolved.styleParam = def.styleParam;
287+
if (!resolved.shaderPath.has_value())
288+
resolved.shaderPath = def.shaderPath;
289+
if (!resolved.shaderParams.has_value())
290+
resolved.shaderParams = def.shaderParams;
291+
}
292+
261293
return resolved;
262294
}
263295

@@ -341,7 +373,8 @@ AnimationProfileTree AnimationProfileTree::defaultTree()
341373
global.timingMode = TimingMode::Easing;
342374
global.duration = 300;
343375
global.easingCurve = QStringLiteral("0.33,1.00,0.68,1.00"); // ease-out cubic
344-
global.spring = SpringParams{1.0, 800.0, 0.0001};
376+
global.spring = SpringParams{ConfigDefaults::springDampingRatio(), ConfigDefaults::springStiffness(),
377+
ConfigDefaults::springEpsilon()};
345378
global.style = AnimationStyle::Morph;
346379
tree.setProfile(QStringLiteral("global"), global);
347380

src/core/baseshaderregistry.cpp

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,8 @@ QVariantList BaseShaderRegistry::shaderPresetsVariant(const QString& id) const
481481
QVariantList result;
482482
for (auto it = info.presets.constBegin(); it != info.presets.constEnd(); ++it) {
483483
QVariantMap entry;
484-
entry[QStringLiteral("name")] = it.key();
485-
entry[QStringLiteral("params")] = it.value();
484+
entry[QLatin1String("name")] = it.key();
485+
entry[QLatin1String("params")] = it.value();
486486
result.append(entry);
487487
}
488488
return result;
@@ -640,71 +640,71 @@ QVariantMap BaseShaderRegistry::translateParamsToUniforms(const QString& id, con
640640
QVariantMap BaseShaderRegistry::shaderInfoToVariantMap(const BaseShaderInfo& info) const
641641
{
642642
QVariantMap map;
643-
map[QStringLiteral("id")] = info.id.isEmpty() ? QStringLiteral("unknown") : info.id;
644-
map[QStringLiteral("name")] = info.name.isEmpty() ? info.id : info.name;
645-
map[QStringLiteral("description")] = info.description;
646-
map[QStringLiteral("author")] = info.author;
647-
map[QStringLiteral("version")] = info.version;
648-
map[QStringLiteral("isUserShader")] = info.isUserShader;
649-
map[QStringLiteral("category")] = info.category;
650-
map[QStringLiteral("isValid")] = info.isValid();
643+
map[QLatin1String("id")] = info.id.isEmpty() ? QStringLiteral("unknown") : info.id;
644+
map[QLatin1String("name")] = info.name.isEmpty() ? info.id : info.name;
645+
map[QLatin1String("description")] = info.description;
646+
map[QLatin1String("author")] = info.author;
647+
map[QLatin1String("version")] = info.version;
648+
map[QLatin1String("isUserShader")] = info.isUserShader;
649+
map[QLatin1String("category")] = info.category;
650+
map[QLatin1String("isValid")] = info.isValid();
651651

652652
if (info.shaderUrl.isValid()) {
653-
map[QStringLiteral("shaderUrl")] = info.shaderUrl.toString();
653+
map[QLatin1String("shaderUrl")] = info.shaderUrl.toString();
654654
} else {
655-
map[QStringLiteral("shaderUrl")] = QString();
655+
map[QLatin1String("shaderUrl")] = QString();
656656
}
657657

658658
if (!info.previewPath.isEmpty()) {
659-
map[QStringLiteral("previewPath")] = info.previewPath;
659+
map[QLatin1String("previewPath")] = info.previewPath;
660660
} else {
661-
map[QStringLiteral("previewPath")] = QString();
661+
map[QLatin1String("previewPath")] = QString();
662662
}
663663

664664
// Parameters list
665665
QVariantList params;
666666
for (const ParameterInfo& param : info.parameters) {
667667
params.append(parameterInfoToVariantMap(param));
668668
}
669-
map[QStringLiteral("parameters")] = params;
669+
map[QLatin1String("parameters")] = params;
670670

671671
// Presets list
672672
QVariantList presetsList;
673673
for (auto it = info.presets.constBegin(); it != info.presets.constEnd(); ++it) {
674674
QVariantMap presetMap;
675-
presetMap[QStringLiteral("name")] = it.key();
676-
presetMap[QStringLiteral("params")] = it.value();
675+
presetMap[QLatin1String("name")] = it.key();
676+
presetMap[QLatin1String("params")] = it.value();
677677
presetsList.append(presetMap);
678678
}
679-
map[QStringLiteral("presets")] = presetsList;
679+
map[QLatin1String("presets")] = presetsList;
680680

681681
return map;
682682
}
683683

684684
QVariantMap BaseShaderRegistry::parameterInfoToVariantMap(const ParameterInfo& param) const
685685
{
686686
QVariantMap map;
687-
map[QStringLiteral("id")] = param.id;
688-
map[QStringLiteral("name")] = param.name;
689-
map[QStringLiteral("type")] = param.type;
690-
map[QStringLiteral("slot")] = param.slot;
691-
map[QStringLiteral("mapsTo")] = param.uniformName();
692-
map[QStringLiteral("useZoneColor")] = param.useZoneColor;
687+
map[QLatin1String("id")] = param.id;
688+
map[QLatin1String("name")] = param.name;
689+
map[QLatin1String("type")] = param.type;
690+
map[QLatin1String("slot")] = param.slot;
691+
map[QLatin1String("mapsTo")] = param.uniformName();
692+
map[QLatin1String("useZoneColor")] = param.useZoneColor;
693693
if (!param.wrap.isEmpty()) {
694-
map[QStringLiteral("wrap")] = param.wrap;
694+
map[QLatin1String("wrap")] = param.wrap;
695695
}
696696

697697
if (!param.group.isEmpty()) {
698-
map[QStringLiteral("group")] = param.group;
698+
map[QLatin1String("group")] = param.group;
699699
}
700700
if (param.defaultValue.isValid()) {
701-
map[QStringLiteral("default")] = param.defaultValue;
701+
map[QLatin1String("default")] = param.defaultValue;
702702
}
703703
if (param.minValue.isValid()) {
704-
map[QStringLiteral("min")] = param.minValue;
704+
map[QLatin1String("min")] = param.minValue;
705705
}
706706
if (param.maxValue.isValid()) {
707-
map[QStringLiteral("max")] = param.maxValue;
707+
map[QLatin1String("max")] = param.maxValue;
708708
}
709709

710710
return map;

src/core/geometryutils.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,13 @@ QString serializeRotationEntriesWithAnimation(const QVector<RotationEntry>& entr
364364
QJsonArray array;
365365
for (const RotationEntry& entry : entries) {
366366
QJsonObject obj;
367-
obj[QStringLiteral("windowId")] = entry.windowId;
368-
obj[QStringLiteral("sourceZoneId")] = entry.sourceZoneId;
369-
obj[QStringLiteral("targetZoneId")] = entry.targetZoneId;
370-
obj[QStringLiteral("x")] = entry.targetGeometry.x();
371-
obj[QStringLiteral("y")] = entry.targetGeometry.y();
372-
obj[QStringLiteral("width")] = entry.targetGeometry.width();
373-
obj[QStringLiteral("height")] = entry.targetGeometry.height();
367+
obj[QLatin1String("windowId")] = entry.windowId;
368+
obj[QLatin1String("sourceZoneId")] = entry.sourceZoneId;
369+
obj[QLatin1String("targetZoneId")] = entry.targetZoneId;
370+
obj[QLatin1String("x")] = entry.targetGeometry.x();
371+
obj[QLatin1String("y")] = entry.targetGeometry.y();
372+
obj[QLatin1String("width")] = entry.targetGeometry.width();
373+
obj[QLatin1String("height")] = entry.targetGeometry.height();
374374

375375
// Resolve animation profile: __restore__ entries use zoneSnapOut
376376
QString event =

src/dbus/windowtrackingadaptor.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include "plasmazones_export.h"
7+
#include "../core/animationprofile.h"
78
#include "../core/windowtrackingservice.h"
89
#include <QObject>
910
#include <QDBusAbstractAdaptor>
@@ -907,6 +908,13 @@ private Q_SLOTS:
907908
*/
908909
QString resolveScreenForSnap(const QString& callerScreen, const QString& zoneId) const;
909910

911+
/**
912+
* @brief Resolve animation profile from settings, returning empty profile if settings unavailable
913+
* @param eventName Animation event name (e.g. "zoneSnapIn", "zoneSnapOut")
914+
* @return Resolved profile, or default-constructed AnimationProfile{} if no settings
915+
*/
916+
AnimationProfile resolvedProfileOrDefault(const QString& eventName) const;
917+
910918
/**
911919
* @brief Apply a successful SnapResult: assign outputs, mark auto-snapped,
912920
* clear floating state, and track the zone assignment.

src/dbus/windowtrackingadaptor/convenience.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313

1414
namespace PlasmaZones {
1515

16+
AnimationProfile WindowTrackingAdaptor::resolvedProfileOrDefault(const QString& eventName) const
17+
{
18+
if (m_settings) {
19+
return m_settings->animationProfileTree().resolvedProfile(eventName);
20+
}
21+
return AnimationProfile{};
22+
}
23+
1624
void WindowTrackingAdaptor::moveWindowToZone(const QString& windowId, const QString& zoneId)
1725
{
1826
if (!validateWindowId(windowId, QStringLiteral("moveWindowToZone"))) {
@@ -39,8 +47,7 @@ void WindowTrackingAdaptor::moveWindowToZone(const QString& windowId, const QStr
3947
m_service->recordSnapIntent(windowId, true);
4048

4149
// Request compositor to apply geometry (with animation metadata)
42-
auto snapInProfile = m_settings ? m_settings->animationProfileTree().resolvedProfile(QStringLiteral("zoneSnapIn"))
43-
: AnimationProfile{};
50+
auto snapInProfile = resolvedProfileOrDefault(QStringLiteral("zoneSnapIn"));
4451
QString geometryJson = GeometryUtils::rectToJsonWithAnimation(geo, snapInProfile);
4552
Q_EMIT applyGeometryRequested(windowId, geometryJson, zoneId, screenId);
4653

@@ -88,8 +95,7 @@ void WindowTrackingAdaptor::swapWindowsById(const QString& windowId1, const QStr
8895
windowSnapped(windowId2, zoneId1, screen1);
8996

9097
// Emit geometry requests for both (with animation metadata)
91-
auto snapInProfile = m_settings ? m_settings->animationProfileTree().resolvedProfile(QStringLiteral("zoneSnapIn"))
92-
: AnimationProfile{};
98+
auto snapInProfile = resolvedProfileOrDefault(QStringLiteral("zoneSnapIn"));
9399
Q_EMIT applyGeometryRequested(windowId1, GeometryUtils::rectToJsonWithAnimation(geo1, snapInProfile), zoneId2,
94100
screen2);
95101
Q_EMIT applyGeometryRequested(windowId2, GeometryUtils::rectToJsonWithAnimation(geo2, snapInProfile), zoneId1,

0 commit comments

Comments
 (0)