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