1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
#include "ScrollTapeController.hpp"
#include "ScrollingAlgorithm.hpp"
#include <algorithm>
#include <cmath>
using namespace Layout::Tiled;
CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) {
;
}
void CScrollTapeController::setDirection(eScrollDirection dir) {
m_direction = dir;
}
eScrollDirection CScrollTapeController::getDirection() const {
return m_direction;
}
bool CScrollTapeController::isPrimaryHorizontal() const {
return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT;
}
bool CScrollTapeController::isReversed() const {
return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP;
}
size_t CScrollTapeController::stripCount() const {
return m_strips.size();
}
SStripData& CScrollTapeController::getStrip(size_t index) {
return m_strips[index];
}
const SStripData& CScrollTapeController::getStrip(size_t index) const {
return m_strips[index];
}
void CScrollTapeController::setOffset(double offset) {
m_offset = offset;
}
double CScrollTapeController::getOffset() const {
return m_offset;
}
void CScrollTapeController::adjustOffset(double delta) {
m_offset += delta;
}
size_t CScrollTapeController::addStrip(float size) {
m_strips.emplace_back();
m_strips.back().size = size;
return m_strips.size() - 1;
}
void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
if (afterIndex >= m_strips.size()) {
addStrip(size);
return;
}
SStripData newStrip;
newStrip.size = size;
m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);
}
void CScrollTapeController::removeStrip(size_t index) {
if (index < m_strips.size())
m_strips.erase(m_strips.begin() + index);
}
double CScrollTapeController::getPrimary(const Vector2D& v) const {
return isPrimaryHorizontal() ? v.x : v.y;
}
double CScrollTapeController::getSecondary(const Vector2D& v) const {
return isPrimaryHorizontal() ? v.y : v.x;
}
void CScrollTapeController::setPrimary(Vector2D& v, double val) const {
if (isPrimaryHorizontal())
v.x = val;
else
v.y = val;
}
void CScrollTapeController::setSecondary(Vector2D& v, double val) const {
if (isPrimaryHorizontal())
v.y = val;
else
v.x = val;
}
Vector2D CScrollTapeController::makeVector(double primary, double secondary) const {
if (isPrimaryHorizontal())
return {primary, secondary};
else
return {secondary, primary};
}
double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const {
if (m_strips.empty())
return 0.0;
if (fullscreenOnOne && m_strips.size() == 1)
return getPrimary(usableArea.size());
double total = 0.0;
const double usablePrimary = getPrimary(usableArea.size());
for (const auto& strip : m_strips) {
total += usablePrimary * strip.size;
}
return total;
}
double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return 0.0;
const double usablePrimary = getPrimary(usableArea.size());
double current = 0.0;
for (size_t i = 0; i < stripIndex; ++i) {
const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size;
current += stripSize;
}
return current;
}
double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return 0.0;
const double usablePrimary = getPrimary(usableArea.size());
if (fullscreenOnOne && m_strips.size() == 1)
return usablePrimary;
return usablePrimary * m_strips[stripIndex].size;
}
CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return {};
const auto& strip = m_strips[stripIndex];
if (targetIndex >= strip.targetSizes.size())
return {};
const double usableSecondary = getSecondary(usableArea.size());
const double usablePrimary = getPrimary(usableArea.size());
const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne);
// calculate position along primary axis (strip position)
double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
// calculate position along secondary axis (within strip)
double secondaryPos = 0.0;
for (size_t i = 0; i < targetIndex; ++i) {
secondaryPos += strip.targetSizes[i] * usableSecondary;
}
double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;
// apply camera offset based on direction
// for RIGHT/DOWN: scroll offset moves content left/up (subtract)
// for LEFT/UP: scroll offset moves content right/down (different coordinate system)
if (m_direction == SCROLL_DIR_LEFT) {
// LEFT: flip the entire primary axis, then apply offset
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
} else if (m_direction == SCROLL_DIR_UP) {
// UP: flip the entire primary axis, then apply offset
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
} else {
// RIGHT/DOWN: normal offset
primaryPos -= cameraOffset;
}
// create the box in primary/secondary coordinates
Vector2D pos = makeVector(primaryPos, secondaryPos);
Vector2D size = makeVector(primarySize, secondarySize);
// translate to workspace position
pos = pos + workspaceOffset;
return CBox{pos, size};
}
double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {
const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne);
const double usablePrimary = getPrimary(usableArea.size());
// don't adjust the offset if we are dragging
if (isBeingDragged())
return m_offset;
// if the content fits in viewport, center it
if (maxExtent < usablePrimary)
m_offset = std::round((maxExtent - usablePrimary) / 2.0);
// if the offset is negative but we already extended, reset offset to 0
if (maxExtent > usablePrimary && m_offset < 0.0)
m_offset = 0.0;
return m_offset;
}
Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) {
const double offset = calculateCameraOffset(usableArea, fullscreenOnOne);
if (isReversed())
return makeVector(offset, 0.0);
else
return makeVector(-offset, 0.0);
}
void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return;
const double usablePrimary = getPrimary(usableArea.size());
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
m_offset = stripStart - (usablePrimary - stripSize) / 2.0;
}
void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return;
const double usablePrimary = getPrimary(usableArea.size());
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart);
}
bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return false;
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
const double viewStart = m_offset;
const double viewEnd = m_offset + getPrimary(usableArea.size());
return stripStart < viewEnd && viewStart < stripEnd;
}
size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const {
if (m_strips.empty())
return 0;
const double usablePrimary = getPrimary(usableArea.size());
double currentPos = m_offset;
for (size_t i = 0; i < m_strips.size(); ++i) {
const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne);
currentPos += stripSize;
if (currentPos >= usablePrimary / 2.0 - 2.0)
return i;
}
return m_strips.empty() ? 0 : m_strips.size() - 1;
}
void CScrollTapeController::swapStrips(size_t a, size_t b) {
if (a >= m_strips.size() || b >= m_strips.size())
return;
std::swap(m_strips.at(a), m_strips.at(b));
}
bool CScrollTapeController::isBeingDragged() const {
for (const auto& s : m_strips) {
if (!s.userData)
continue;
for (const auto& d : s.userData->targetDatas) {
if (d->target == g_layoutManager->dragController()->target())
return true;
}
}
return false;
}
|