1 #include <algorithm>
2 #include <cmath>
3 #include <functional>
4 #include <iomanip>
5 #include <mutex>
6 #include <set>
7 #include <sstream>
8
9 #include "Common/Data/Text/I18n.h"
10 #include "Common/Input/KeyCodes.h"
11 #include "Common/Math/curves.h"
12 #include "Common/UI/Context.h"
13 #include "Common/UI/Tween.h"
14 #include "Common/UI/Root.h"
15 #include "Common/UI/View.h"
16 #include "Common/UI/ViewGroup.h"
17 #include "Common/Render/DrawBuffer.h"
18
19 #include "Common/Log.h"
20 #include "Common/TimeUtil.h"
21 #include "Common/StringUtils.h"
22
23 namespace UI {
24
25 const float ITEM_HEIGHT = 64.f;
26
ApplyGravity(const Bounds outer,const Margins & margins,float w,float h,int gravity,Bounds & inner)27 void ApplyGravity(const Bounds outer, const Margins &margins, float w, float h, int gravity, Bounds &inner) {
28 inner.w = w;
29 inner.h = h;
30
31 switch (gravity & G_HORIZMASK) {
32 case G_LEFT: inner.x = outer.x + margins.left; break;
33 case G_RIGHT: inner.x = outer.x + outer.w - w - margins.right; break;
34 case G_HCENTER: inner.x = outer.x + (outer.w - w) / 2; break;
35 }
36
37 switch (gravity & G_VERTMASK) {
38 case G_TOP: inner.y = outer.y + margins.top; break;
39 case G_BOTTOM: inner.y = outer.y + outer.h - h - margins.bottom; break;
40 case G_VCENTER: inner.y = outer.y + (outer.h - h) / 2; break;
41 }
42 }
43
~ViewGroup()44 ViewGroup::~ViewGroup() {
45 // Tear down the contents recursively.
46 Clear();
47 }
48
RemoveSubview(View * view)49 void ViewGroup::RemoveSubview(View *view) {
50 std::lock_guard<std::mutex> guard(modifyLock_);
51 for (size_t i = 0; i < views_.size(); i++) {
52 if (views_[i] == view) {
53 views_.erase(views_.begin() + i);
54 delete view;
55 return;
56 }
57 }
58 }
59
ContainsSubview(const View * view) const60 bool ViewGroup::ContainsSubview(const View *view) const {
61 for (const View *subview : views_) {
62 if (subview == view || subview->ContainsSubview(view))
63 return true;
64 }
65 return false;
66 }
67
Clear()68 void ViewGroup::Clear() {
69 std::lock_guard<std::mutex> guard(modifyLock_);
70 for (size_t i = 0; i < views_.size(); i++) {
71 delete views_[i];
72 views_[i] = nullptr;
73 }
74 views_.clear();
75 }
76
PersistData(PersistStatus status,std::string anonId,PersistMap & storage)77 void ViewGroup::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
78 std::lock_guard<std::mutex> guard(modifyLock_);
79
80 std::string tag = Tag();
81 if (tag.empty()) {
82 tag = anonId;
83 }
84
85 for (size_t i = 0; i < views_.size(); i++) {
86 views_[i]->PersistData(status, tag + "/" + StringFromInt((int)i), storage);
87 }
88 }
89
Touch(const TouchInput & input)90 void ViewGroup::Touch(const TouchInput &input) {
91 std::lock_guard<std::mutex> guard(modifyLock_);
92 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
93 // TODO: If there is a transformation active, transform input coordinates accordingly.
94 if ((*iter)->GetVisibility() == V_VISIBLE)
95 (*iter)->Touch(input);
96 }
97 }
98
Query(float x,float y,std::vector<View * > & list)99 void ViewGroup::Query(float x, float y, std::vector<View *> &list) {
100 if (bounds_.Contains(x, y)) {
101 list.push_back(this);
102 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
103 (*iter)->Query(x, y, list);
104 }
105 }
106 }
107
Key(const KeyInput & input)108 bool ViewGroup::Key(const KeyInput &input) {
109 std::lock_guard<std::mutex> guard(modifyLock_);
110 bool ret = false;
111 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
112 // TODO: If there is a transformation active, transform input coordinates accordingly.
113 if ((*iter)->GetVisibility() == V_VISIBLE)
114 ret = ret || (*iter)->Key(input);
115 }
116 return ret;
117 }
118
Axis(const AxisInput & input)119 void ViewGroup::Axis(const AxisInput &input) {
120 std::lock_guard<std::mutex> guard(modifyLock_);
121 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
122 // TODO: If there is a transformation active, transform input coordinates accordingly.
123 if ((*iter)->GetVisibility() == V_VISIBLE)
124 (*iter)->Axis(input);
125 }
126 }
127
DeviceLost()128 void ViewGroup::DeviceLost() {
129 std::lock_guard<std::mutex> guard(modifyLock_);
130 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
131 (*iter)->DeviceLost();
132 }
133 }
134
DeviceRestored(Draw::DrawContext * draw)135 void ViewGroup::DeviceRestored(Draw::DrawContext *draw) {
136 std::lock_guard<std::mutex> guard(modifyLock_);
137 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
138 (*iter)->DeviceRestored(draw);
139 }
140 }
141
Draw(UIContext & dc)142 void ViewGroup::Draw(UIContext &dc) {
143 if (hasDropShadow_) {
144 // Darken things behind.
145 dc.FillRect(UI::Drawable(0x60000000), dc.GetBounds().Expand(dropShadowExpand_));
146 float dropsize = 30.0f;
147 dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid,
148 bounds_.x - dropsize, bounds_.y,
149 bounds_.x2() + dropsize, bounds_.y2()+dropsize*1.5f, 0xDF000000, 3.0f);
150 }
151
152 if (clip_) {
153 dc.PushScissor(bounds_);
154 }
155
156 dc.FillRect(bg_, bounds_);
157 for (View *view : views_) {
158 if (view->GetVisibility() == V_VISIBLE) {
159 // Check if bounds are in current scissor rectangle.
160 if (dc.GetScissorBounds().Intersects(dc.TransformBounds(view->GetBounds())))
161 view->Draw(dc);
162 }
163 }
164 if (clip_) {
165 dc.PopScissor();
166 }
167 }
168
DescribeText() const169 std::string ViewGroup::DescribeText() const {
170 std::stringstream ss;
171 bool needNewline = false;
172 for (View *view : views_) {
173 if (view->GetVisibility() != V_VISIBLE)
174 continue;
175 std::string s = view->DescribeText();
176 if (s.empty())
177 continue;
178
179 if (needNewline) {
180 ss << "\n";
181 }
182 ss << s;
183 needNewline = s[s.length() - 1] != '\n';
184 }
185 return ss.str();
186 }
187
DescribeListUnordered(const char * heading) const188 std::string ViewGroup::DescribeListUnordered(const char *heading) const {
189 std::stringstream ss;
190 ss << heading << "\n";
191
192 bool needNewline = false;
193 for (View *view : views_) {
194 if (view->GetVisibility() != V_VISIBLE)
195 continue;
196 std::string s = view->DescribeText();
197 if (s.empty())
198 continue;
199
200 ss << " - " << IndentString(s, " ", true);
201 }
202 return ss.str();
203 }
204
DescribeListOrdered(const char * heading) const205 std::string ViewGroup::DescribeListOrdered(const char *heading) const {
206 std::stringstream ss;
207 ss << heading << "\n";
208
209 // This is how much space we need for the highest number.
210 int sz = (int)floorf(log10f((float)views_.size())) + 1;
211 std::string indent = " " + std::string(sz, ' ');
212
213 bool needNewline = false;
214 int n = 1;
215 for (View *view : views_) {
216 if (view->GetVisibility() != V_VISIBLE)
217 continue;
218 std::string s = view->DescribeText();
219 if (s.empty())
220 continue;
221
222 ss << std::setw(sz) << n++ << ". " << IndentString(s, indent, true);
223 }
224 return ss.str();
225 }
226
Update()227 void ViewGroup::Update() {
228 View::Update();
229 for (View *view : views_) {
230 if (view->GetVisibility() != V_GONE)
231 view->Update();
232 }
233 }
234
SetFocus()235 bool ViewGroup::SetFocus() {
236 std::lock_guard<std::mutex> guard(modifyLock_);
237 if (!CanBeFocused() && !views_.empty()) {
238 for (size_t i = 0; i < views_.size(); i++) {
239 if (views_[i]->SetFocus())
240 return true;
241 }
242 }
243 return false;
244 }
245
SubviewFocused(View * view)246 bool ViewGroup::SubviewFocused(View *view) {
247 for (size_t i = 0; i < views_.size(); i++) {
248 if (views_[i] == view)
249 return true;
250 if (views_[i]->SubviewFocused(view))
251 return true;
252 }
253 return false;
254 }
255
256 // Returns the percentage the smaller one overlaps the bigger one.
HorizontalOverlap(const Bounds & a,const Bounds & b)257 static float HorizontalOverlap(const Bounds &a, const Bounds &b) {
258 if (a.x2() < b.x || b.x2() < a.x)
259 return 0.0f;
260 // okay they do overlap. Let's clip.
261 float maxMin = std::max(a.x, b.x);
262 float minMax = std::min(a.x2(), b.x2());
263 float overlap = minMax - maxMin;
264 if (overlap < 0.0f)
265 return 0.0f;
266 else
267 return std::min(1.0f, overlap / std::min(a.w, b.w));
268 }
269
270 // Returns the percentage the smaller one overlaps the bigger one.
VerticalOverlap(const Bounds & a,const Bounds & b)271 static float VerticalOverlap(const Bounds &a, const Bounds &b) {
272 if (a.y2() < b.y || b.y2() < a.y)
273 return 0.0f;
274 // okay they do overlap. Let's clip.
275 float maxMin = std::max(a.y, b.y);
276 float minMax = std::min(a.y2(), b.y2());
277 float overlap = minMax - maxMin;
278 if (overlap < 0.0f)
279 return 0.0f;
280 else
281 return std::min(1.0f, overlap / std::min(a.h, b.h));
282 }
283
GetTargetScore(const Point & originPos,int originIndex,View * origin,View * destination,FocusDirection direction)284 float GetTargetScore(const Point &originPos, int originIndex, View *origin, View *destination, FocusDirection direction) {
285 // Skip labels and things like that.
286 if (!destination->CanBeFocused())
287 return 0.0f;
288 if (destination->IsEnabled() == false)
289 return 0.0f;
290 if (destination->GetVisibility() != V_VISIBLE)
291 return 0.0f;
292
293 Point destPos = destination->GetFocusPosition(Opposite(direction));
294
295 float dx = destPos.x - originPos.x;
296 float dy = destPos.y - originPos.y;
297
298 float distance = sqrtf(dx*dx + dy*dy);
299 float overlap = 0.0f;
300 float dirX = dx / distance;
301 float dirY = dy / distance;
302
303 bool wrongDirection = false;
304 bool vertical = false;
305 float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds());
306 float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds());
307 if (horizOverlap == 1.0f && vertOverlap == 1.0f) {
308 if (direction != FOCUS_PREV_PAGE && direction != FOCUS_NEXT_PAGE) {
309 INFO_LOG(SYSTEM, "Contain overlap");
310 return 0.0;
311 }
312 }
313 float originSize = 0.0f;
314 switch (direction) {
315 case FOCUS_LEFT:
316 overlap = vertOverlap;
317 originSize = origin->GetBounds().w;
318 if (dirX > 0.0f) {
319 wrongDirection = true;
320 }
321 break;
322 case FOCUS_UP:
323 overlap = horizOverlap;
324 originSize = origin->GetBounds().h;
325 if (dirY > 0.0f) {
326 wrongDirection = true;
327 }
328 vertical = true;
329 break;
330 case FOCUS_RIGHT:
331 overlap = vertOverlap;
332 originSize = origin->GetBounds().w;
333 if (dirX < 0.0f) {
334 wrongDirection = true;
335 }
336 break;
337 case FOCUS_DOWN:
338 overlap = horizOverlap;
339 originSize = origin->GetBounds().h;
340 if (dirY < 0.0f) {
341 wrongDirection = true;
342 }
343 vertical = true;
344 break;
345 case FOCUS_FIRST:
346 if (originIndex == -1)
347 return 0.0f;
348 if (dirX > 0.0f || dirY > 0.0f)
349 return 0.0f;
350 // More distance is good.
351 return distance;
352 case FOCUS_LAST:
353 if (originIndex == -1)
354 return 0.0f;
355 if (dirX < 0.0f || dirY < 0.0f)
356 return 0.0f;
357 // More distance is good.
358 return distance;
359 case FOCUS_PREV_PAGE:
360 case FOCUS_NEXT_PAGE:
361 // Not always, but let's go with the bonus on height.
362 vertical = true;
363 break;
364 case FOCUS_PREV:
365 case FOCUS_NEXT:
366 ERROR_LOG(SYSTEM, "Invalid focus direction");
367 break;
368 }
369
370 // At large distances, ignore overlap.
371 if (distance > 2.0 * originSize)
372 overlap = 0.0f;
373
374 if (wrongDirection) {
375 return 0.0f;
376 } else {
377 return 10.0f / std::max(1.0f, distance) + overlap * 2.0;
378 }
379 }
380
GetDirectionScore(int originIndex,View * origin,View * destination,FocusDirection direction)381 float GetDirectionScore(int originIndex, View *origin, View *destination, FocusDirection direction) {
382 Point originPos = origin->GetFocusPosition(direction);
383 return GetTargetScore(originPos, originIndex, origin, destination, direction);
384 }
385
FindNeighbor(View * view,FocusDirection direction,NeighborResult result)386 NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, NeighborResult result) {
387 if (!IsEnabled())
388 return result;
389 if (GetVisibility() != V_VISIBLE)
390 return result;
391
392 // First, find the position of the view in the list.
393 int num = -1;
394 for (size_t i = 0; i < views_.size(); i++) {
395 if (views_[i] == view) {
396 num = (int)i;
397 break;
398 }
399 }
400
401 if (direction == FOCUS_PREV || direction == FOCUS_NEXT) {
402 switch (direction) {
403 case FOCUS_PREV:
404 // If view not found, no neighbor to find.
405 if (num == -1)
406 return NeighborResult(0, 0.0f);
407 return NeighborResult(views_[(num + views_.size() - 1) % views_.size()], 0.0f);
408
409 case FOCUS_NEXT:
410 // If view not found, no neighbor to find.
411 if (num == -1)
412 return NeighborResult(0, 0.0f);
413 return NeighborResult(views_[(num + 1) % views_.size()], 0.0f);
414 default:
415 return NeighborResult(nullptr, 0.0f);
416 }
417 }
418
419 switch (direction) {
420 case FOCUS_UP:
421 case FOCUS_LEFT:
422 case FOCUS_RIGHT:
423 case FOCUS_DOWN:
424 case FOCUS_FIRST:
425 case FOCUS_LAST:
426 {
427 // First, try the child views themselves as candidates
428 for (size_t i = 0; i < views_.size(); i++) {
429 if (views_[i] == view)
430 continue;
431
432 float score = GetDirectionScore(num, view, views_[i], direction);
433 if (score > result.score) {
434 result.score = score;
435 result.view = views_[i];
436 }
437 }
438
439 // Then go right ahead and see if any of the children contain any better candidates.
440 for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
441 if ((*iter)->IsViewGroup()) {
442 ViewGroup *vg = static_cast<ViewGroup *>(*iter);
443 if (vg)
444 result = vg->FindNeighbor(view, direction, result);
445 }
446 }
447
448 // Boost neighbors with the same parent
449 if (num != -1) {
450 //result.score += 100.0f;
451 }
452 return result;
453 }
454
455 case FOCUS_PREV_PAGE:
456 case FOCUS_NEXT_PAGE:
457 return FindScrollNeighbor(view, Point(INFINITY, INFINITY), direction, result);
458
459 default:
460 return result;
461 }
462 }
463
FindScrollNeighbor(View * view,const Point & target,FocusDirection direction,NeighborResult best)464 NeighborResult ViewGroup::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) {
465 if (!IsEnabled())
466 return best;
467 if (GetVisibility() != V_VISIBLE)
468 return best;
469
470 if (target.x < INFINITY && target.y < INFINITY) {
471 for (auto v : views_) {
472 // Note: we consider the origin itself, which might already be the best option.
473 float score = GetTargetScore(target, -1, view, v, direction);
474 if (score > best.score) {
475 best.score = score;
476 best.view = v;
477 }
478 }
479 }
480 for (auto v : views_) {
481 if (v->IsViewGroup()) {
482 ViewGroup *vg = static_cast<ViewGroup *>(v);
483 if (vg)
484 best = vg->FindScrollNeighbor(view, target, direction, best);
485 }
486 }
487 return best;
488 }
489
490 // TODO: This code needs some cleanup/restructuring...
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)491 void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
492 MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
493 MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
494
495 if (views_.empty())
496 return;
497
498 float sum = 0.0f;
499 float maxOther = 0.0f;
500 float totalWeight = 0.0f;
501 float weightSum = 0.0f;
502 float weightZeroSum = 0.0f;
503
504 int numVisible = 0;
505
506 for (View *view : views_) {
507 if (view->GetVisibility() == V_GONE)
508 continue;
509 numVisible++;
510
511 const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As<LinearLayoutParams>();
512
513 Margins margins = defaultMargins_;
514
515 if (linLayoutParams) {
516 totalWeight += linLayoutParams->weight;
517 if (linLayoutParams->HasMargins())
518 margins = linLayoutParams->margins;
519 }
520
521 if (orientation_ == ORIENT_HORIZONTAL) {
522 MeasureSpec v = vert;
523 if (v.type == UNSPECIFIED && measuredHeight_ != 0.0f)
524 v = MeasureSpec(AT_MOST, measuredHeight_);
525 view->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v - (float)margins.vert());
526 if (horiz.type == AT_MOST && view->GetMeasuredWidth() + margins.horiz() > horiz.size - weightZeroSum) {
527 // Try again, this time with AT_MOST.
528 view->Measure(dc, horiz, v - (float)margins.vert());
529 }
530 } else if (orientation_ == ORIENT_VERTICAL) {
531 MeasureSpec h = horiz;
532 if (h.type == UNSPECIFIED && measuredWidth_ != 0.0f)
533 h = MeasureSpec(AT_MOST, measuredWidth_);
534 view->Measure(dc, h - (float)margins.horiz(), MeasureSpec(UNSPECIFIED, measuredHeight_));
535 if (vert.type == AT_MOST && view->GetMeasuredHeight() + margins.vert() > vert.size - weightZeroSum) {
536 // Try again, this time with AT_MOST.
537 view->Measure(dc, h - (float)margins.horiz(), vert);
538 }
539 }
540
541 float amount;
542 if (orientation_ == ORIENT_HORIZONTAL) {
543 amount = view->GetMeasuredWidth() + margins.horiz();
544 maxOther = std::max(maxOther, view->GetMeasuredHeight() + margins.vert());
545 } else {
546 amount = view->GetMeasuredHeight() + margins.vert();
547 maxOther = std::max(maxOther, view->GetMeasuredWidth() + margins.horiz());
548 }
549
550 sum += amount;
551 if (linLayoutParams) {
552 if (linLayoutParams->weight == 0.0f)
553 weightZeroSum += amount;
554
555 weightSum += linLayoutParams->weight;
556 } else {
557 weightZeroSum += amount;
558 }
559 }
560
561 weightZeroSum += spacing_ * (numVisible - 1);
562
563 // Alright, got the sum. Let's take the remaining space after the fixed-size views,
564 // and distribute among the weighted ones.
565 if (orientation_ == ORIENT_HORIZONTAL) {
566 MeasureBySpec(layoutParams_->width, weightZeroSum, horiz, &measuredWidth_);
567
568 // If we've got stretch, allow growing to fill the parent.
569 float allowedWidth = measuredWidth_;
570 if (horiz.type == AT_MOST && measuredWidth_ < horiz.size) {
571 allowedWidth = horiz.size;
572 }
573
574 float usedWidth = 0.0f;
575
576 // Redistribute the stretchy ones! and remeasure the children!
577 for (View *view : views_) {
578 if (view->GetVisibility() == V_GONE)
579 continue;
580 const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As<LinearLayoutParams>();
581
582 if (linLayoutParams && linLayoutParams->weight > 0.0f) {
583 Margins margins = defaultMargins_;
584 if (linLayoutParams->HasMargins())
585 margins = linLayoutParams->margins;
586 MeasureSpec v = vert;
587 if (v.type == UNSPECIFIED && measuredHeight_ != 0.0f)
588 v = MeasureSpec(AT_MOST, measuredHeight_);
589 float unit = (allowedWidth - weightZeroSum) / weightSum;
590 MeasureSpec h(AT_MOST, unit * linLayoutParams->weight - margins.horiz());
591 if (horiz.type == EXACTLY) {
592 h.type = EXACTLY;
593 }
594 view->Measure(dc, h, v - (float)margins.vert());
595 usedWidth += view->GetMeasuredWidth();
596 maxOther = std::max(maxOther, view->GetMeasuredHeight() + margins.vert());
597 }
598 }
599
600 if (horiz.type == AT_MOST && measuredWidth_ < horiz.size) {
601 measuredWidth_ += usedWidth;
602 }
603
604 // Measure here in case maxOther moved (can happen due to word wrap.)
605 MeasureBySpec(layoutParams_->height, maxOther, vert, &measuredHeight_);
606 } else {
607 MeasureBySpec(layoutParams_->height, weightZeroSum, vert, &measuredHeight_);
608
609 // If we've got stretch, allow growing to fill the parent.
610 float allowedHeight = measuredHeight_;
611 if (vert.type == AT_MOST && measuredHeight_ < vert.size) {
612 allowedHeight = vert.size;
613 }
614
615 float usedHeight = 0.0f;
616
617 // Redistribute the stretchy ones! and remeasure the children!
618 for (View *view : views_) {
619 if (view->GetVisibility() == V_GONE)
620 continue;
621 const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As<LinearLayoutParams>();
622
623 if (linLayoutParams && linLayoutParams->weight > 0.0f) {
624 Margins margins = defaultMargins_;
625 if (linLayoutParams->HasMargins())
626 margins = linLayoutParams->margins;
627 MeasureSpec h = horiz;
628 if (h.type == UNSPECIFIED && measuredWidth_ != 0.0f)
629 h = MeasureSpec(AT_MOST, measuredWidth_);
630 float unit = (allowedHeight - weightZeroSum) / weightSum;
631 MeasureSpec v(AT_MOST, unit * linLayoutParams->weight - margins.vert());
632 if (vert.type == EXACTLY) {
633 v.type = EXACTLY;
634 }
635 view->Measure(dc, h - (float)margins.horiz(), v);
636 usedHeight += view->GetMeasuredHeight();
637 maxOther = std::max(maxOther, view->GetMeasuredWidth() + margins.horiz());
638 }
639 }
640
641 if (vert.type == AT_MOST && measuredHeight_ < vert.size) {
642 measuredHeight_ += usedHeight;
643 }
644
645 // Measure here in case maxOther moved (can happen due to word wrap.)
646 MeasureBySpec(layoutParams_->width, maxOther, horiz, &measuredWidth_);
647 }
648 }
649
650 // weight != 0 = fill remaining space.
Layout()651 void LinearLayout::Layout() {
652 const Bounds &bounds = bounds_;
653
654 Bounds itemBounds;
655 float pos;
656
657 if (orientation_ == ORIENT_HORIZONTAL) {
658 pos = bounds.x;
659 itemBounds.y = bounds.y;
660 itemBounds.h = measuredHeight_;
661 } else {
662 pos = bounds.y;
663 itemBounds.x = bounds.x;
664 itemBounds.w = measuredWidth_;
665 }
666
667 for (size_t i = 0; i < views_.size(); i++) {
668 if (views_[i]->GetVisibility() == V_GONE)
669 continue;
670
671 const LinearLayoutParams *linLayoutParams = views_[i]->GetLayoutParams()->As<LinearLayoutParams>();
672
673 Gravity gravity = G_TOPLEFT;
674 Margins margins = defaultMargins_;
675 if (linLayoutParams) {
676 if (linLayoutParams->HasMargins())
677 margins = linLayoutParams->margins;
678 gravity = linLayoutParams->gravity;
679 }
680
681 if (orientation_ == ORIENT_HORIZONTAL) {
682 itemBounds.x = pos;
683 itemBounds.w = views_[i]->GetMeasuredWidth() + margins.horiz();
684 } else {
685 itemBounds.y = pos;
686 itemBounds.h = views_[i]->GetMeasuredHeight() + margins.vert();
687 }
688
689 Bounds innerBounds;
690 ApplyGravity(itemBounds, margins,
691 views_[i]->GetMeasuredWidth(), views_[i]->GetMeasuredHeight(),
692 gravity, innerBounds);
693
694 views_[i]->SetBounds(innerBounds);
695 views_[i]->Layout();
696
697 pos += spacing_ + (orientation_ == ORIENT_HORIZONTAL ? itemBounds.w : itemBounds.h);
698 }
699 }
700
DescribeText() const701 std::string LinearLayoutList::DescribeText() const {
702 auto u = GetI18NCategory("UI Elements");
703 return DescribeListOrdered(u->T("List:"));
704 }
705
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)706 void FrameLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
707 if (views_.empty()) {
708 MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
709 MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
710 return;
711 }
712
713 for (size_t i = 0; i < views_.size(); i++) {
714 if (views_[i]->GetVisibility() == V_GONE)
715 continue;
716 views_[i]->Measure(dc, horiz, vert);
717 }
718 }
719
Layout()720 void FrameLayout::Layout() {
721 for (size_t i = 0; i < views_.size(); i++) {
722 if (views_[i]->GetVisibility() == V_GONE)
723 continue;
724 float w = views_[i]->GetMeasuredWidth();
725 float h = views_[i]->GetMeasuredHeight();
726
727 Bounds bounds;
728 bounds.w = w;
729 bounds.h = h;
730
731 bounds.x = bounds_.x + (measuredWidth_ - w) / 2;
732 bounds.y = bounds_.y + (measuredWidth_ - h) / 2;
733 views_[i]->SetBounds(bounds);
734 }
735 }
736
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)737 void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
738 // Respect margins
739 Margins margins;
740 if (views_.size()) {
741 const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As<LinearLayoutParams>();
742 if (linLayoutParams) {
743 margins = linLayoutParams->margins;
744 }
745 }
746
747 // The scroll view itself simply obeys its parent - but also tries to fit the child if possible.
748 MeasureBySpec(layoutParams_->width, horiz.size, horiz, &measuredWidth_);
749 MeasureBySpec(layoutParams_->height, vert.size, vert, &measuredHeight_);
750
751 if (views_.size()) {
752 if (orientation_ == ORIENT_HORIZONTAL) {
753 MeasureSpec v = MeasureSpec(AT_MOST, measuredHeight_ - margins.vert());
754 if (measuredHeight_ == 0.0f && (vert.type == UNSPECIFIED || layoutParams_->height == WRAP_CONTENT)) {
755 v.type = UNSPECIFIED;
756 }
757 views_[0]->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v);
758 MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_);
759 if (layoutParams_->width == WRAP_CONTENT)
760 MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_);
761 } else {
762 MeasureSpec h = MeasureSpec(AT_MOST, measuredWidth_ - margins.horiz());
763 if (measuredWidth_ == 0.0f && (horiz.type == UNSPECIFIED || layoutParams_->width == WRAP_CONTENT)) {
764 h.type = UNSPECIFIED;
765 }
766 views_[0]->Measure(dc, h, MeasureSpec(UNSPECIFIED, measuredHeight_));
767 MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_);
768 if (layoutParams_->height == WRAP_CONTENT)
769 MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_);
770 }
771 if (orientation_ == ORIENT_VERTICAL && vert.type != EXACTLY) {
772 float bestHeight = std::max(views_[0]->GetMeasuredHeight(), views_[0]->GetBounds().h);
773 if (vert.type == AT_MOST)
774 bestHeight = std::min(bestHeight, vert.size);
775
776 if (measuredHeight_ < bestHeight && layoutParams_->height < 0.0f) {
777 measuredHeight_ = bestHeight;
778 }
779 }
780 }
781 }
782
Layout()783 void ScrollView::Layout() {
784 if (!views_.size())
785 return;
786 Bounds scrolled;
787
788 // Respect margins
789 Margins margins;
790 const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As<LinearLayoutParams>();
791 if (linLayoutParams) {
792 margins = linLayoutParams->margins;
793 }
794
795 scrolled.w = views_[0]->GetMeasuredWidth() - margins.horiz();
796 scrolled.h = views_[0]->GetMeasuredHeight() - margins.vert();
797
798 layoutScrollPos_ = ClampedScrollPos(scrollPos_);
799
800 switch (orientation_) {
801 case ORIENT_HORIZONTAL:
802 if (scrolled.w != lastViewSize_) {
803 if (rememberPos_)
804 scrollPos_ = *rememberPos_;
805 lastViewSize_ = scrolled.w;
806 }
807 scrolled.x = bounds_.x - layoutScrollPos_;
808 scrolled.y = bounds_.y + margins.top;
809 break;
810 case ORIENT_VERTICAL:
811 if (scrolled.h != lastViewSize_) {
812 if (rememberPos_)
813 scrollPos_ = *rememberPos_;
814 lastViewSize_ = scrolled.h;
815 }
816 scrolled.x = bounds_.x + margins.left;
817 scrolled.y = bounds_.y - layoutScrollPos_;
818 break;
819 }
820
821 views_[0]->SetBounds(scrolled);
822 views_[0]->Layout();
823 }
824
Key(const KeyInput & input)825 bool ScrollView::Key(const KeyInput &input) {
826 if (visibility_ != V_VISIBLE)
827 return ViewGroup::Key(input);
828
829 if (input.flags & KEY_DOWN) {
830 switch (input.keyCode) {
831 case NKCODE_EXT_MOUSEWHEEL_UP:
832 ScrollRelative(-250);
833 break;
834 case NKCODE_EXT_MOUSEWHEEL_DOWN:
835 ScrollRelative(250);
836 break;
837 }
838 }
839 return ViewGroup::Key(input);
840 }
841
842 const float friction = 0.92f;
843 const float stop_threshold = 0.1f;
844
Touch(const TouchInput & input)845 void ScrollView::Touch(const TouchInput &input) {
846 if ((input.flags & TOUCH_DOWN) && scrollTouchId_ == -1) {
847 scrollStart_ = scrollPos_;
848 inertia_ = 0.0f;
849 scrollTouchId_ = input.id;
850 }
851
852 Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL;
853
854 if ((input.flags & TOUCH_UP) && input.id == scrollTouchId_) {
855 float info[4];
856 if (gesture_.GetGestureInfo(gesture, input.id, info)) {
857 inertia_ = info[1];
858 }
859 scrollTouchId_ = -1;
860 }
861
862 TouchInput input2;
863 if (CanScroll()) {
864 input2 = gesture_.Update(input, bounds_);
865 float info[4];
866 if (input.id == scrollTouchId_ && gesture_.GetGestureInfo(gesture, input.id, info) && !(input.flags & TOUCH_DOWN)) {
867 float pos = scrollStart_ - info[0];
868 scrollPos_ = pos;
869 scrollTarget_ = pos;
870 scrollToTarget_ = false;
871 }
872 } else {
873 input2 = input;
874 scrollTarget_ = scrollPos_;
875 scrollToTarget_ = false;
876 }
877
878 if (!(input.flags & TOUCH_DOWN) || bounds_.Contains(input.x, input.y)) {
879 ViewGroup::Touch(input2);
880 }
881 }
882
Draw(UIContext & dc)883 void ScrollView::Draw(UIContext &dc) {
884 if (!views_.size()) {
885 ViewGroup::Draw(dc);
886 return;
887 }
888
889 dc.PushScissor(bounds_);
890 // For debugging layout issues, this can be useful.
891 // dc.FillRect(Drawable(0x60FF00FF), bounds_);
892 views_[0]->Draw(dc);
893 dc.PopScissor();
894
895 float childHeight = views_[0]->GetBounds().h;
896 float scrollMax = std::max(0.0f, childHeight - bounds_.h);
897
898 float ratio = bounds_.h / views_[0]->GetBounds().h;
899
900 float bobWidth = 5;
901 if (ratio < 1.0f && scrollMax > 0.0f) {
902 float bobHeight = ratio * bounds_.h;
903 float bobOffset = (ClampedScrollPos(scrollPos_) / scrollMax) * (bounds_.h - bobHeight);
904
905 Bounds bob(bounds_.x2() - bobWidth, bounds_.y + bobOffset, bobWidth, bobHeight);
906 dc.FillRect(Drawable(0x80FFFFFF), bob);
907 }
908 }
909
SubviewFocused(View * view)910 bool ScrollView::SubviewFocused(View *view) {
911 if (!ViewGroup::SubviewFocused(view))
912 return false;
913
914 const Bounds &vBounds = view->GetBounds();
915
916 // Scroll so that the focused view is visible, and a bit more so that headers etc gets visible too, in most cases.
917 const float overscroll = std::min(view->GetBounds().h / 1.5f, GetBounds().h / 4.0f);
918
919 float pos = ClampedScrollPos(scrollPos_);
920 float visibleSize = orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w;
921 float visibleEnd = scrollPos_ + visibleSize;
922
923 float viewStart, viewEnd;
924 switch (orientation_) {
925 case ORIENT_HORIZONTAL:
926 viewStart = layoutScrollPos_ + vBounds.x - bounds_.x;
927 viewEnd = layoutScrollPos_ + vBounds.x2() - bounds_.x;
928 break;
929 case ORIENT_VERTICAL:
930 viewStart = layoutScrollPos_ + vBounds.y - bounds_.y;
931 viewEnd = layoutScrollPos_ + vBounds.y2() - bounds_.y;
932 break;
933 }
934
935 if (viewEnd > visibleEnd) {
936 ScrollTo(viewEnd - visibleSize + overscroll);
937 } else if (viewStart < pos) {
938 ScrollTo(viewStart - overscroll);
939 }
940
941 return true;
942 }
943
FindScrollNeighbor(View * view,const Point & target,FocusDirection direction,NeighborResult best)944 NeighborResult ScrollView::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) {
945 if (ContainsSubview(view) && views_[0]->IsViewGroup()) {
946 ViewGroup *vg = static_cast<ViewGroup *>(views_[0]);
947 int found = -1;
948 for (int i = 0, n = vg->GetNumSubviews(); i < n; ++i) {
949 View *child = vg->GetViewByIndex(i);
950 if (child == view || child->ContainsSubview(view)) {
951 found = i;
952 break;
953 }
954 }
955
956 // Okay, the previously focused view is inside this.
957 if (found != -1) {
958 float mult = 0.0f;
959 switch (direction) {
960 case FOCUS_PREV_PAGE:
961 mult = -1.0f;
962 break;
963 case FOCUS_NEXT_PAGE:
964 mult = 1.0f;
965 break;
966 default:
967 break;
968 }
969
970 // Okay, now where is our ideal target?
971 Point targetPos = view->GetBounds().Center();
972 if (orientation_ == ORIENT_VERTICAL)
973 targetPos.y += mult * bounds_.h;
974 else
975 targetPos.x += mult * bounds_.x;
976
977 // Okay, which subview is closest to that?
978 best = vg->FindScrollNeighbor(view, targetPos, direction, best);
979 // Avoid reselecting the same view.
980 if (best.view == view)
981 best.view = nullptr;
982 return best;
983 }
984 }
985
986 return ViewGroup::FindScrollNeighbor(view, target, direction, best);
987 }
988
PersistData(PersistStatus status,std::string anonId,PersistMap & storage)989 void ScrollView::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
990 ViewGroup::PersistData(status, anonId, storage);
991
992 std::string tag = Tag();
993 if (tag.empty()) {
994 tag = anonId;
995 }
996
997 PersistBuffer &buffer = storage["ScrollView::" + tag];
998 switch (status) {
999 case PERSIST_SAVE:
1000 {
1001 buffer.resize(1);
1002 float pos = scrollToTarget_ ? scrollTarget_ : scrollPos_;
1003 // Hmm, ugly... better buffer?
1004 buffer[0] = *(int *)&pos;
1005 }
1006 break;
1007
1008 case PERSIST_RESTORE:
1009 if (buffer.size() == 1) {
1010 float pos = *(float *)&buffer[0];
1011 scrollPos_ = pos;
1012 scrollTarget_ = pos;
1013 scrollToTarget_ = false;
1014 }
1015 break;
1016 }
1017 }
1018
SetVisibility(Visibility visibility)1019 void ScrollView::SetVisibility(Visibility visibility) {
1020 ViewGroup::SetVisibility(visibility);
1021
1022 if (visibility == V_GONE && !rememberPos_) {
1023 // Since this is no longer shown, forget the scroll position.
1024 // For example, this happens when switching tabs.
1025 ScrollTo(0.0f);
1026 }
1027 }
1028
ScrollTo(float newScrollPos)1029 void ScrollView::ScrollTo(float newScrollPos) {
1030 scrollTarget_ = newScrollPos;
1031 scrollToTarget_ = true;
1032 }
1033
ScrollRelative(float distance)1034 void ScrollView::ScrollRelative(float distance) {
1035 scrollTarget_ = scrollPos_ + distance;
1036 scrollToTarget_ = true;
1037 }
1038
ClampedScrollPos(float pos)1039 float ScrollView::ClampedScrollPos(float pos) {
1040 if (!views_.size()) {
1041 return 0.0f;
1042 }
1043
1044 float childSize = orientation_ == ORIENT_VERTICAL ? views_[0]->GetBounds().h : views_[0]->GetBounds().w;
1045 float scrollMax = std::max(0.0f, childSize - (orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w));
1046
1047 Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL;
1048
1049 if (scrollTouchId_ >= 0 && gesture_.IsGestureActive(gesture, scrollTouchId_) && bounds_.h > 0) {
1050 float maxPull = bounds_.h * 0.1f;
1051 if (pos < 0.0f) {
1052 float dist = std::min(-pos * (1.0f / bounds_.h), 1.0f);
1053 pull_ = -(sqrt(dist) * maxPull);
1054 } else if (pos > scrollMax) {
1055 float dist = std::min((pos - scrollMax) * (1.0f / bounds_.h), 1.0f);
1056 pull_ = sqrt(dist) * maxPull;
1057 } else {
1058 pull_ = 0.0f;
1059 }
1060 }
1061
1062 if (pos < 0.0f && pos < pull_) {
1063 pos = pull_;
1064 }
1065 if (pos > scrollMax && pos > scrollMax + pull_) {
1066 pos = scrollMax + pull_;
1067 }
1068
1069 return pos;
1070 }
1071
ScrollToBottom()1072 void ScrollView::ScrollToBottom() {
1073 float childHeight = views_[0]->GetBounds().h;
1074 float scrollMax = std::max(0.0f, childHeight - bounds_.h);
1075 scrollPos_ = scrollMax;
1076 scrollTarget_ = scrollMax;
1077 }
1078
CanScroll() const1079 bool ScrollView::CanScroll() const {
1080 if (!views_.size())
1081 return false;
1082 switch (orientation_) {
1083 case ORIENT_VERTICAL:
1084 return views_[0]->GetBounds().h > bounds_.h;
1085 case ORIENT_HORIZONTAL:
1086 return views_[0]->GetBounds().w > bounds_.w;
1087 default:
1088 return false;
1089 }
1090 }
1091
1092 float ScrollView::lastScrollPosX = 0;
1093 float ScrollView::lastScrollPosY = 0;
1094
~ScrollView()1095 ScrollView::~ScrollView() {
1096 lastScrollPosX = 0;
1097 lastScrollPosY = 0;
1098 }
1099
GetLastScrollPosition(float & x,float & y)1100 void ScrollView::GetLastScrollPosition(float &x, float &y) {
1101 x = lastScrollPosX;
1102 y = lastScrollPosY;
1103 }
1104
Update()1105 void ScrollView::Update() {
1106 if (visibility_ != V_VISIBLE) {
1107 inertia_ = 0.0f;
1108 }
1109 ViewGroup::Update();
1110 float oldPos = scrollPos_;
1111
1112 Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL;
1113 gesture_.UpdateFrame();
1114 if (scrollToTarget_) {
1115 float target = ClampedScrollPos(scrollTarget_);
1116
1117 inertia_ = 0.0f;
1118 if (fabsf(target - scrollPos_) < 0.5f) {
1119 scrollPos_ = target;
1120 scrollToTarget_ = false;
1121 } else {
1122 scrollPos_ += (target - scrollPos_) * 0.3f;
1123 }
1124 } else if (inertia_ != 0.0f && !gesture_.IsGestureActive(gesture, scrollTouchId_)) {
1125 scrollPos_ -= inertia_;
1126 inertia_ *= friction;
1127 if (fabsf(inertia_) < stop_threshold)
1128 inertia_ = 0.0f;
1129 }
1130
1131 if (!gesture_.IsGestureActive(gesture, scrollTouchId_)) {
1132 scrollPos_ = ClampedScrollPos(scrollPos_);
1133
1134 pull_ *= friction;
1135 if (fabsf(pull_) < 0.01f) {
1136 pull_ = 0.0f;
1137 }
1138 }
1139
1140 if (oldPos != scrollPos_)
1141 orientation_ == ORIENT_HORIZONTAL ? lastScrollPosX = scrollPos_ : lastScrollPosY = scrollPos_;
1142
1143 // We load some lists asynchronously, so don't update the position until it's loaded.
1144 if (rememberPos_ && ClampedScrollPos(scrollPos_) != ClampedScrollPos(*rememberPos_)) {
1145 *rememberPos_ = scrollPos_;
1146 }
1147 }
1148
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)1149 void AnchorLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
1150 MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
1151 MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
1152
1153 MeasureViews(dc, horiz, vert);
1154
1155 const bool unspecifiedWidth = layoutParams_->width == WRAP_CONTENT && (overflow_ || horiz.type == UNSPECIFIED);
1156 const bool unspecifiedHeight = layoutParams_->height == WRAP_CONTENT && (overflow_ || vert.type == UNSPECIFIED);
1157 if (unspecifiedWidth || unspecifiedHeight) {
1158 // Give everything another chance to size, given the new measurements.
1159 MeasureSpec h = unspecifiedWidth ? MeasureSpec(AT_MOST, measuredWidth_) : horiz;
1160 MeasureSpec v = unspecifiedHeight ? MeasureSpec(AT_MOST, measuredHeight_) : vert;
1161 MeasureViews(dc, h, v);
1162 }
1163 }
1164
MeasureViews(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)1165 void AnchorLayout::MeasureViews(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
1166 for (size_t i = 0; i < views_.size(); i++) {
1167 Size width = WRAP_CONTENT;
1168 Size height = WRAP_CONTENT;
1169
1170 MeasureSpec specW(UNSPECIFIED, measuredWidth_);
1171 MeasureSpec specH(UNSPECIFIED, measuredHeight_);
1172
1173 if (!overflow_) {
1174 if (horiz.type != UNSPECIFIED) {
1175 specW = MeasureSpec(AT_MOST, horiz.size);
1176 }
1177 if (vert.type != UNSPECIFIED) {
1178 specH = MeasureSpec(AT_MOST, vert.size);
1179 }
1180 }
1181
1182 const AnchorLayoutParams *params = views_[i]->GetLayoutParams()->As<AnchorLayoutParams>();
1183 if (params) {
1184 width = params->width;
1185 height = params->height;
1186
1187 if (!params->center) {
1188 if (params->left > NONE && params->right > NONE) {
1189 width = measuredWidth_ - params->left - params->right;
1190 }
1191 if (params->top > NONE && params->bottom > NONE) {
1192 height = measuredHeight_ - params->top - params->bottom;
1193 }
1194 }
1195 if (width >= 0) {
1196 specW = MeasureSpec(EXACTLY, width);
1197 }
1198 if (height >= 0) {
1199 specH = MeasureSpec(EXACTLY, height);
1200 }
1201 }
1202
1203 views_[i]->Measure(dc, specW, specH);
1204
1205 if (layoutParams_->width == WRAP_CONTENT)
1206 measuredWidth_ = std::max(measuredWidth_, views_[i]->GetMeasuredWidth());
1207 if (layoutParams_->height == WRAP_CONTENT)
1208 measuredHeight_ = std::max(measuredHeight_, views_[i]->GetMeasuredHeight());
1209 }
1210 }
1211
Layout()1212 void AnchorLayout::Layout() {
1213 for (size_t i = 0; i < views_.size(); i++) {
1214 const AnchorLayoutParams *params = views_[i]->GetLayoutParams()->As<AnchorLayoutParams>();
1215
1216 Bounds vBounds;
1217 vBounds.w = views_[i]->GetMeasuredWidth();
1218 vBounds.h = views_[i]->GetMeasuredHeight();
1219
1220 // Clamp width/height to our own
1221 if (vBounds.w > bounds_.w) vBounds.w = bounds_.w;
1222 if (vBounds.h > bounds_.h) vBounds.h = bounds_.h;
1223
1224 float left = 0, top = 0, right = 0, bottom = 0;
1225 bool center = false;
1226 if (params) {
1227 left = params->left;
1228 top = params->top;
1229 right = params->right;
1230 bottom = params->bottom;
1231 center = params->center;
1232 }
1233
1234 if (left > NONE) {
1235 vBounds.x = bounds_.x + left;
1236 if (center)
1237 vBounds.x -= vBounds.w * 0.5f;
1238 } else if (right > NONE) {
1239 vBounds.x = bounds_.x2() - right - vBounds.w;
1240 if (center) {
1241 vBounds.x += vBounds.w * 0.5f;
1242 }
1243 }
1244
1245 if (top > NONE) {
1246 vBounds.y = bounds_.y + top;
1247 if (center)
1248 vBounds.y -= vBounds.h * 0.5f;
1249 } else if (bottom > NONE) {
1250 vBounds.y = bounds_.y2() - bottom - vBounds.h;
1251 if (center)
1252 vBounds.y += vBounds.h * 0.5f;
1253 }
1254
1255 views_[i]->SetBounds(vBounds);
1256 views_[i]->Layout();
1257 }
1258 }
1259
GridLayout(GridLayoutSettings settings,LayoutParams * layoutParams)1260 GridLayout::GridLayout(GridLayoutSettings settings, LayoutParams *layoutParams)
1261 : ViewGroup(layoutParams), settings_(settings), numColumns_(1) {
1262 if (settings.orientation != ORIENT_HORIZONTAL)
1263 ERROR_LOG(SYSTEM, "GridLayout: Vertical layouts not yet supported");
1264 }
1265
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)1266 void GridLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
1267 MeasureSpecType measureType = settings_.fillCells ? EXACTLY : AT_MOST;
1268
1269 for (size_t i = 0; i < views_.size(); i++) {
1270 views_[i]->Measure(dc, MeasureSpec(measureType, settings_.columnWidth), MeasureSpec(measureType, settings_.rowHeight));
1271 }
1272
1273 // Use the max possible width so AT_MOST gives us the full size.
1274 float maxWidth = (settings_.columnWidth + settings_.spacing) * views_.size() + settings_.spacing;
1275 MeasureBySpec(layoutParams_->width, maxWidth, horiz, &measuredWidth_);
1276
1277 // Okay, got the width we are supposed to adjust to. Now we can calculate the number of columns.
1278 numColumns_ = (measuredWidth_ - settings_.spacing) / (settings_.columnWidth + settings_.spacing);
1279 if (!numColumns_) numColumns_ = 1;
1280 int numRows = (int)(views_.size() + (numColumns_ - 1)) / numColumns_;
1281
1282 float estimatedHeight = (settings_.rowHeight + settings_.spacing) * numRows;
1283
1284 MeasureBySpec(layoutParams_->height, estimatedHeight, vert, &measuredHeight_);
1285 }
1286
Layout()1287 void GridLayout::Layout() {
1288 int y = 0;
1289 int x = 0;
1290 int count = 0;
1291 for (size_t i = 0; i < views_.size(); i++) {
1292 const GridLayoutParams *lp = views_[i]->GetLayoutParams()->As<GridLayoutParams>();
1293 Bounds itemBounds, innerBounds;
1294 Gravity grav = lp ? lp->gravity : G_CENTER;
1295
1296 itemBounds.x = bounds_.x + x;
1297 itemBounds.y = bounds_.y + y;
1298 itemBounds.w = settings_.columnWidth;
1299 itemBounds.h = settings_.rowHeight;
1300
1301 ApplyGravity(itemBounds, Margins(0.0f),
1302 views_[i]->GetMeasuredWidth(), views_[i]->GetMeasuredHeight(),
1303 grav, innerBounds);
1304
1305 views_[i]->SetBounds(innerBounds);
1306 views_[i]->Layout();
1307
1308 count++;
1309 if (count == numColumns_) {
1310 count = 0;
1311 x = 0;
1312 y += itemBounds.h + settings_.spacing;
1313 } else {
1314 x += itemBounds.w + settings_.spacing;
1315 }
1316 }
1317 }
1318
DescribeText() const1319 std::string GridLayoutList::DescribeText() const {
1320 auto u = GetI18NCategory("UI Elements");
1321 return DescribeListOrdered(u->T("List:"));
1322 }
1323
TabHolder(Orientation orientation,float stripSize,LayoutParams * layoutParams)1324 TabHolder::TabHolder(Orientation orientation, float stripSize, LayoutParams *layoutParams)
1325 : LinearLayout(Opposite(orientation), layoutParams), stripSize_(stripSize) {
1326 SetSpacing(0.0f);
1327 if (orientation == ORIENT_HORIZONTAL) {
1328 tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
1329 tabStrip_->SetTopTabs(true);
1330 tabScroll_ = new ScrollView(orientation, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
1331 tabScroll_->Add(tabStrip_);
1332 Add(tabScroll_);
1333 } else {
1334 tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(stripSize, WRAP_CONTENT));
1335 tabStrip_->SetTopTabs(true);
1336 Add(tabStrip_);
1337 }
1338 tabStrip_->OnChoice.Handle(this, &TabHolder::OnTabClick);
1339
1340 contents_ = new AnchorLayout(new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));
1341 Add(contents_)->SetClip(true);
1342 }
1343
AddTabContents(const std::string & title,View * tabContents)1344 void TabHolder::AddTabContents(const std::string &title, View *tabContents) {
1345 tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
1346 tabs_.push_back(tabContents);
1347 tabStrip_->AddChoice(title);
1348 contents_->Add(tabContents);
1349 if (tabs_.size() > 1)
1350 tabContents->SetVisibility(V_GONE);
1351
1352 // Will be filled in later.
1353 tabTweens_.push_back(nullptr);
1354 }
1355
SetCurrentTab(int tab,bool skipTween)1356 void TabHolder::SetCurrentTab(int tab, bool skipTween) {
1357 if (tab >= (int)tabs_.size()) {
1358 // Ignore
1359 return;
1360 }
1361
1362 auto setupTween = [&](View *view, AnchorTranslateTween *&tween) {
1363 if (tween)
1364 return;
1365
1366 tween = new AnchorTranslateTween(0.15f, bezierEaseInOut);
1367 tween->Finish.Add([&](EventParams &e) {
1368 e.v->SetVisibility(tabs_[currentTab_] == e.v ? V_VISIBLE : V_GONE);
1369 return EVENT_DONE;
1370 });
1371 view->AddTween(tween)->Persist();
1372 };
1373
1374 if (tab != currentTab_) {
1375 Orientation orient = Opposite(orientation_);
1376 // Direction from which the new tab will come.
1377 float dir = tab < currentTab_ ? -1.0f : 1.0f;
1378
1379 // First, setup any missing tweens.
1380 setupTween(tabs_[currentTab_], tabTweens_[currentTab_]);
1381 setupTween(tabs_[tab], tabTweens_[tab]);
1382
1383 // Currently displayed, so let's reset it.
1384 if (skipTween) {
1385 tabs_[currentTab_]->SetVisibility(V_GONE);
1386 tabTweens_[tab]->Reset(Point(0.0f, 0.0f));
1387 tabTweens_[tab]->Apply(tabs_[tab]);
1388 } else {
1389 tabTweens_[currentTab_]->Reset(Point(0.0f, 0.0f));
1390
1391 if (orient == ORIENT_HORIZONTAL) {
1392 tabTweens_[tab]->Reset(Point(bounds_.w * dir, 0.0f));
1393 tabTweens_[currentTab_]->Divert(Point(bounds_.w * -dir, 0.0f));
1394 } else {
1395 tabTweens_[tab]->Reset(Point(0.0f, bounds_.h * dir));
1396 tabTweens_[currentTab_]->Divert(Point(0.0f, bounds_.h * -dir));
1397 }
1398 // Actually move it to the initial position now, just to avoid any flicker.
1399 tabTweens_[tab]->Apply(tabs_[tab]);
1400 tabTweens_[tab]->Divert(Point(0.0f, 0.0f));
1401 }
1402 tabs_[tab]->SetVisibility(V_VISIBLE);
1403
1404 currentTab_ = tab;
1405 }
1406 tabStrip_->SetSelection(tab, false);
1407 }
1408
OnTabClick(EventParams & e)1409 EventReturn TabHolder::OnTabClick(EventParams &e) {
1410 // We have e.b set when it was an explicit click action.
1411 // In that case, we make the view gone and then visible - this scrolls scrollviews to the top.
1412 if (e.b != 0) {
1413 SetCurrentTab((int)e.a);
1414 }
1415 return EVENT_DONE;
1416 }
1417
PersistData(PersistStatus status,std::string anonId,PersistMap & storage)1418 void TabHolder::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
1419 ViewGroup::PersistData(status, anonId, storage);
1420
1421 std::string tag = Tag();
1422 if (tag.empty()) {
1423 tag = anonId;
1424 }
1425
1426 PersistBuffer &buffer = storage["TabHolder::" + tag];
1427 switch (status) {
1428 case PERSIST_SAVE:
1429 buffer.resize(1);
1430 buffer[0] = currentTab_;
1431 break;
1432
1433 case PERSIST_RESTORE:
1434 if (buffer.size() == 1) {
1435 SetCurrentTab(buffer[0], true);
1436 }
1437 break;
1438 }
1439 }
1440
ChoiceStrip(Orientation orientation,LayoutParams * layoutParams)1441 ChoiceStrip::ChoiceStrip(Orientation orientation, LayoutParams *layoutParams)
1442 : LinearLayout(orientation, layoutParams), selected_(0), topTabs_(false) {
1443 SetSpacing(0.0f);
1444 }
1445
AddChoice(const std::string & title)1446 void ChoiceStrip::AddChoice(const std::string &title) {
1447 StickyChoice *c = new StickyChoice(title, "",
1448 orientation_ == ORIENT_HORIZONTAL ?
1449 nullptr :
1450 new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
1451 c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
1452 Add(c);
1453 if (selected_ == (int)views_.size() - 1)
1454 c->Press();
1455 }
1456
AddChoice(ImageID buttonImage)1457 void ChoiceStrip::AddChoice(ImageID buttonImage) {
1458 StickyChoice *c = new StickyChoice(buttonImage,
1459 orientation_ == ORIENT_HORIZONTAL ?
1460 nullptr :
1461 new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
1462 c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
1463 Add(c);
1464 if (selected_ == (int)views_.size() - 1)
1465 c->Press();
1466 }
1467
OnChoiceClick(EventParams & e)1468 EventReturn ChoiceStrip::OnChoiceClick(EventParams &e) {
1469 // Unstick the other choices that weren't clicked.
1470 for (int i = 0; i < (int)views_.size(); i++) {
1471 if (views_[i] != e.v) {
1472 Choice(i)->Release();
1473 } else {
1474 selected_ = i;
1475 }
1476 }
1477
1478 EventParams e2{};
1479 e2.v = views_[selected_];
1480 e2.a = selected_;
1481 // Set to 1 to indicate an explicit click.
1482 e2.b = 1;
1483 // Dispatch immediately (we're already on the UI thread as we're in an event handler).
1484 return OnChoice.Dispatch(e2);
1485 }
1486
SetSelection(int sel,bool triggerClick)1487 void ChoiceStrip::SetSelection(int sel, bool triggerClick) {
1488 int prevSelected = selected_;
1489 StickyChoice *prevChoice = Choice(selected_);
1490 if (prevChoice)
1491 prevChoice->Release();
1492 selected_ = sel;
1493 StickyChoice *newChoice = Choice(selected_);
1494 if (newChoice) {
1495 newChoice->Press();
1496
1497 if (topTabs_ && prevSelected != selected_) {
1498 EventParams e{};
1499 e.v = views_[selected_];
1500 e.a = selected_;
1501 // Set to 0 to indicate a selection change (not a click.)
1502 e.b = triggerClick ? 1 : 0;
1503 OnChoice.Trigger(e);
1504 }
1505 }
1506 }
1507
HighlightChoice(unsigned int choice)1508 void ChoiceStrip::HighlightChoice(unsigned int choice){
1509 if (choice < (unsigned int)views_.size()){
1510 Choice(choice)->HighlightChanged(true);
1511 }
1512 };
1513
Key(const KeyInput & input)1514 bool ChoiceStrip::Key(const KeyInput &input) {
1515 bool ret = false;
1516 if (topTabs_ && (input.flags & KEY_DOWN)) {
1517 if (IsTabLeftKey(input)) {
1518 if (selected_ > 0) {
1519 SetSelection(selected_ - 1, true);
1520 UI::PlayUISound(UI::UISound::TOGGLE_OFF); // Maybe make specific sounds for this at some point?
1521 }
1522 ret = true;
1523 } else if (IsTabRightKey(input)) {
1524 if (selected_ < (int)views_.size() - 1) {
1525 SetSelection(selected_ + 1, true);
1526 UI::PlayUISound(UI::UISound::TOGGLE_ON);
1527 }
1528 ret = true;
1529 }
1530 }
1531 return ret || ViewGroup::Key(input);
1532 }
1533
Draw(UIContext & dc)1534 void ChoiceStrip::Draw(UIContext &dc) {
1535 ViewGroup::Draw(dc);
1536 if (topTabs_) {
1537 if (orientation_ == ORIENT_HORIZONTAL)
1538 dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 4, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color );
1539 else if (orientation_ == ORIENT_VERTICAL)
1540 dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x2() - 4, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color );
1541 }
1542 }
1543
DescribeText() const1544 std::string ChoiceStrip::DescribeText() const {
1545 auto u = GetI18NCategory("UI Elements");
1546 return DescribeListUnordered(u->T("Choices:"));
1547 }
1548
Choice(int index)1549 StickyChoice *ChoiceStrip::Choice(int index) {
1550 if ((size_t)index < views_.size())
1551 return static_cast<StickyChoice *>(views_[index]);
1552 return nullptr;
1553 }
ListView(ListAdaptor * a,std::set<int> hidden,LayoutParams * layoutParams)1554 ListView::ListView(ListAdaptor *a, std::set<int> hidden, LayoutParams *layoutParams)
1555 : ScrollView(ORIENT_VERTICAL, layoutParams), adaptor_(a), maxHeight_(0), hidden_(hidden) {
1556
1557 linLayout_ = new LinearLayout(ORIENT_VERTICAL);
1558 linLayout_->SetSpacing(0.0f);
1559 Add(linLayout_);
1560 CreateAllItems();
1561 }
1562
CreateAllItems()1563 void ListView::CreateAllItems() {
1564 linLayout_->Clear();
1565 // Let's not be clever yet, we'll just create them all up front and add them all in.
1566 for (int i = 0; i < adaptor_->GetNumItems(); i++) {
1567 if (hidden_.find(i) == hidden_.end()) {
1568 View *v = linLayout_->Add(adaptor_->CreateItemView(i));
1569 adaptor_->AddEventCallback(v, std::bind(&ListView::OnItemCallback, this, i, std::placeholders::_1));
1570 }
1571 }
1572 }
1573
Measure(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert)1574 void ListView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
1575 ScrollView::Measure(dc, horiz, vert);
1576 if (maxHeight_ > 0 && measuredHeight_ > maxHeight_) {
1577 measuredHeight_ = maxHeight_;
1578 }
1579 }
1580
DescribeText() const1581 std::string ListView::DescribeText() const {
1582 auto u = GetI18NCategory("UI Elements");
1583 return DescribeListOrdered(u->T("List:"));
1584 }
1585
OnItemCallback(int num,EventParams & e)1586 EventReturn ListView::OnItemCallback(int num, EventParams &e) {
1587 EventParams ev{};
1588 ev.v = nullptr;
1589 ev.a = num;
1590 adaptor_->SetSelected(num);
1591 OnChoice.Trigger(ev);
1592 CreateAllItems();
1593 return EVENT_DONE;
1594 }
1595
CreateItemView(int index)1596 View *ChoiceListAdaptor::CreateItemView(int index) {
1597 return new Choice(items_[index]);
1598 }
1599
AddEventCallback(View * view,std::function<EventReturn (EventParams &)> callback)1600 bool ChoiceListAdaptor::AddEventCallback(View *view, std::function<EventReturn(EventParams&)> callback) {
1601 Choice *choice = (Choice *)view;
1602 choice->OnClick.Add(callback);
1603 return EVENT_DONE;
1604 }
1605
1606
CreateItemView(int index)1607 View *StringVectorListAdaptor::CreateItemView(int index) {
1608 return new Choice(items_[index], "", index == selected_);
1609 }
1610
AddEventCallback(View * view,std::function<EventReturn (EventParams &)> callback)1611 bool StringVectorListAdaptor::AddEventCallback(View *view, std::function<EventReturn(EventParams&)> callback) {
1612 Choice *choice = (Choice *)view;
1613 choice->OnClick.Add(callback);
1614 return EVENT_DONE;
1615 }
1616
1617 } // namespace UI
1618