2013-12-03 16:47:15 +01:00
|
|
|
#include <set>
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
#include "base/display.h"
|
2013-06-09 11:18:57 +02:00
|
|
|
#include "base/functional.h"
|
2013-05-03 00:21:39 +02:00
|
|
|
#include "base/logging.h"
|
2013-08-12 23:07:27 +02:00
|
|
|
#include "base/mutex.h"
|
2013-07-08 12:34:39 +02:00
|
|
|
#include "input/keycodes.h"
|
2013-05-28 00:32:00 +02:00
|
|
|
#include "ui/ui_context.h"
|
2013-05-03 00:21:39 +02:00
|
|
|
#include "ui/view.h"
|
|
|
|
#include "ui/viewgroup.h"
|
2013-06-08 22:41:17 +02:00
|
|
|
#include "gfx_es2/draw_buffer.h"
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-09-17 00:48:09 -04:00
|
|
|
#include <algorithm>
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
namespace UI {
|
|
|
|
|
2013-06-08 22:41:17 +02:00
|
|
|
const float ITEM_HEIGHT = 64.f;
|
|
|
|
|
2013-10-23 13:55:11 +02:00
|
|
|
static recursive_mutex focusLock;
|
|
|
|
static std::vector<int> focusMoves;
|
|
|
|
|
2014-01-31 14:31:19 +01:00
|
|
|
bool dragCaptured[MAX_POINTERS];
|
|
|
|
|
|
|
|
void CaptureDrag(int id) {
|
|
|
|
dragCaptured[id] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReleaseDrag(int id) {
|
|
|
|
dragCaptured[id] = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsDragCaptured(int id) {
|
|
|
|
return dragCaptured[id];
|
|
|
|
}
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
void ApplyGravity(const Bounds outer, const Margins &margins, float w, float h, int gravity, Bounds &inner) {
|
|
|
|
inner.w = w - (margins.left + margins.right);
|
|
|
|
inner.h = h - (margins.right + margins.left);
|
|
|
|
|
|
|
|
switch (gravity & G_HORIZMASK) {
|
|
|
|
case G_LEFT: inner.x = outer.x + margins.left; break;
|
|
|
|
case G_RIGHT: inner.x = outer.x + outer.w - w - margins.right; break;
|
|
|
|
case G_HCENTER: inner.x = outer.x + (outer.w - w) / 2; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (gravity & G_VERTMASK) {
|
|
|
|
case G_TOP: inner.y = outer.y + margins.top; break;
|
|
|
|
case G_BOTTOM: inner.y = outer.y + outer.h - h - margins.bottom; break;
|
|
|
|
case G_VCENTER: inner.y = outer.y + (outer.h - h) / 2; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ViewGroup::~ViewGroup() {
|
|
|
|
// Tear down the contents recursively.
|
2013-07-16 00:25:08 +02:00
|
|
|
Clear();
|
|
|
|
}
|
|
|
|
|
2013-08-16 12:51:57 +02:00
|
|
|
void ViewGroup::RemoveSubview(View *view) {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-08-16 12:51:57 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
if (views_[i] == view) {
|
|
|
|
views_.erase(views_.begin() + i);
|
|
|
|
delete view;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-16 00:25:08 +02:00
|
|
|
void ViewGroup::Clear() {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-06-09 01:21:08 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
delete views_[i];
|
|
|
|
views_[i] = 0;
|
|
|
|
}
|
|
|
|
views_.clear();
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ViewGroup::Touch(const TouchInput &input) {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-05-03 00:21:39 +02:00
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
|
|
|
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
2013-06-02 23:44:28 +02:00
|
|
|
if ((*iter)->GetVisibility() == V_VISIBLE)
|
|
|
|
(*iter)->Touch(input);
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-08 12:34:39 +02:00
|
|
|
void ViewGroup::Key(const KeyInput &input) {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-07-08 12:34:39 +02:00
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
|
|
|
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
|
|
|
if ((*iter)->GetVisibility() == V_VISIBLE)
|
|
|
|
(*iter)->Key(input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-12 23:07:27 +02:00
|
|
|
void ViewGroup::Axis(const AxisInput &input) {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-08-12 23:07:27 +02:00
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
|
|
|
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
|
|
|
if ((*iter)->GetVisibility() == V_VISIBLE)
|
|
|
|
(*iter)->Axis(input);
|
|
|
|
}
|
|
|
|
}
|
2013-07-08 12:34:39 +02:00
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void ViewGroup::Draw(UIContext &dc) {
|
2013-07-16 00:25:08 +02:00
|
|
|
if (hasDropShadow_) {
|
|
|
|
// Darken things behind.
|
|
|
|
dc.FillRect(UI::Drawable(0x60000000), Bounds(0,0,dp_xres, dp_yres));
|
2013-08-16 16:47:25 +02:00
|
|
|
float dropsize = 30;
|
2013-12-06 16:44:39 +01:00
|
|
|
dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid,
|
|
|
|
bounds_.x - dropsize, bounds_.y,
|
|
|
|
bounds_.x2() + dropsize, bounds_.y2()+dropsize*1.5, 0xDF000000, 3.0f);
|
2013-07-16 00:25:08 +02:00
|
|
|
}
|
|
|
|
|
2013-08-20 12:27:42 +02:00
|
|
|
if (clip_) {
|
|
|
|
dc.PushScissor(bounds_);
|
|
|
|
}
|
|
|
|
|
2013-07-16 00:25:08 +02:00
|
|
|
dc.FillRect(bg_, bounds_);
|
2013-05-03 00:21:39 +02:00
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
|
|
|
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
2013-06-10 22:05:58 +02:00
|
|
|
if ((*iter)->GetVisibility() == V_VISIBLE) {
|
|
|
|
// Check if bounds are in current scissor rectangle.
|
|
|
|
if (dc.GetScissorBounds().Intersects((*iter)->GetBounds()))
|
|
|
|
(*iter)->Draw(dc);
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
2013-08-20 12:27:42 +02:00
|
|
|
if (clip_) {
|
|
|
|
dc.PopScissor();
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-25 12:40:57 +02:00
|
|
|
void ViewGroup::Update(const InputState &input_state) {
|
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
|
|
|
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
2013-06-02 23:44:28 +02:00
|
|
|
if ((*iter)->GetVisibility() != V_GONE)
|
|
|
|
(*iter)->Update(input_state);
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ViewGroup::SetFocus() {
|
2013-08-16 14:02:28 +02:00
|
|
|
lock_guard guard(modifyLock_);
|
2013-05-25 12:40:57 +02:00
|
|
|
if (!CanBeFocused() && !views_.empty()) {
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
if (views_[i]->SetFocus())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-05-27 21:39:56 +02:00
|
|
|
bool ViewGroup::SubviewFocused(View *view) {
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
if (views_[i] == view)
|
|
|
|
return true;
|
|
|
|
if (views_[i]->SubviewFocused(view))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-10-23 13:55:11 +02:00
|
|
|
static float HorizontalOverlap(const Bounds &a, const Bounds &b) {
|
|
|
|
if (a.x2() < b.x || b.x2() < a.x)
|
|
|
|
return 0.0f;
|
|
|
|
// okay they do overlap. Let's clip.
|
|
|
|
float maxMin = std::max(a.x, b.x);
|
|
|
|
float minMax = std::min(a.x2(), b.x2());
|
|
|
|
float overlap = minMax - maxMin;
|
|
|
|
if (overlap < 0.0f)
|
|
|
|
return 0.0f;
|
|
|
|
else
|
|
|
|
return std::min(1.0f, overlap / std::min(a.w, b.w));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the percentage the smaller one overlaps the bigger one.
|
|
|
|
static float VerticalOverlap(const Bounds &a, const Bounds &b) {
|
|
|
|
if (a.y2() < b.y || b.y2() < a.y)
|
|
|
|
return 0.0f;
|
|
|
|
// okay they do overlap. Let's clip.
|
|
|
|
float maxMin = std::max(a.y, b.y);
|
|
|
|
float minMax = std::min(a.y2(), b.y2());
|
|
|
|
float overlap = minMax - maxMin;
|
|
|
|
if (overlap < 0.0f)
|
|
|
|
return 0.0f;
|
|
|
|
else
|
|
|
|
return std::min(1.0f, overlap / std::min(a.h, b.h));
|
|
|
|
}
|
|
|
|
|
2013-05-27 00:54:02 +02:00
|
|
|
float GetDirectionScore(View *origin, View *destination, FocusDirection direction) {
|
|
|
|
// Skip labels and things like that.
|
|
|
|
if (!destination->CanBeFocused())
|
|
|
|
return 0.0f;
|
2013-08-18 23:11:26 +02:00
|
|
|
if (destination->IsEnabled() == false)
|
2013-06-02 23:44:28 +02:00
|
|
|
return 0.0f;
|
2013-06-08 22:41:17 +02:00
|
|
|
if (destination->GetVisibility() != V_VISIBLE)
|
|
|
|
return 0.0f;
|
2013-05-25 12:40:57 +02:00
|
|
|
|
2013-07-15 19:56:36 +02:00
|
|
|
Point originPos = origin->GetFocusPosition(direction);
|
|
|
|
Point destPos = destination->GetFocusPosition(Opposite(direction));
|
|
|
|
|
|
|
|
float dx = destPos.x - originPos.x;
|
|
|
|
float dy = destPos.y - originPos.y;
|
2013-05-27 00:54:02 +02:00
|
|
|
|
2013-10-23 13:55:11 +02:00
|
|
|
float distance = sqrtf(dx*dx + dy*dy);
|
|
|
|
float overlap = 0.0f;
|
2013-05-27 00:54:02 +02:00
|
|
|
float dirX = dx / distance;
|
|
|
|
float dirY = dy / distance;
|
|
|
|
|
2013-10-23 13:55:11 +02:00
|
|
|
bool wrongDirection = false;
|
2013-12-01 15:36:18 +01:00
|
|
|
bool vertical = false;
|
2013-10-23 13:55:11 +02:00
|
|
|
float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds());
|
|
|
|
float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds());
|
|
|
|
if (horizOverlap == 1.0f && vertOverlap == 1.0f) {
|
|
|
|
ILOG("Contain overlap");
|
|
|
|
return 0.0;
|
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
switch (direction) {
|
|
|
|
case FOCUS_LEFT:
|
2013-10-23 13:55:11 +02:00
|
|
|
overlap = vertOverlap;
|
|
|
|
if (dirX > 0.0f) {
|
|
|
|
wrongDirection = true;
|
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
break;
|
|
|
|
case FOCUS_UP:
|
2013-10-23 13:55:11 +02:00
|
|
|
overlap = horizOverlap;
|
|
|
|
if (dirY > 0.0f) {
|
|
|
|
wrongDirection = true;
|
|
|
|
}
|
2013-12-01 15:36:18 +01:00
|
|
|
vertical = true;
|
2013-05-27 00:54:02 +02:00
|
|
|
break;
|
|
|
|
case FOCUS_RIGHT:
|
2013-10-23 13:55:11 +02:00
|
|
|
overlap = vertOverlap;
|
|
|
|
if (dirX < 0.0f) {
|
|
|
|
wrongDirection = true;
|
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
break;
|
|
|
|
case FOCUS_DOWN:
|
2013-10-23 13:55:11 +02:00
|
|
|
overlap = horizOverlap;
|
|
|
|
if (dirY < 0.0f) {
|
|
|
|
wrongDirection = true;
|
|
|
|
}
|
2013-12-01 15:36:18 +01:00
|
|
|
vertical = true;
|
2013-05-27 00:54:02 +02:00
|
|
|
break;
|
2013-10-19 14:29:32 -07:00
|
|
|
case FOCUS_PREV:
|
|
|
|
case FOCUS_NEXT:
|
|
|
|
ELOG("Invalid focus direction");
|
|
|
|
break;
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
|
2013-12-01 15:36:18 +01:00
|
|
|
// Add a small bonus if the views are the same size. This prioritizes moving to the next item
|
|
|
|
// upwards in a scroll view instead of moving up to the top bar.
|
|
|
|
float bonus = 0.0f;
|
|
|
|
if (vertical) {
|
|
|
|
float widthDifference = origin->GetBounds().w - destination->GetBounds().w;
|
|
|
|
if (widthDifference == 0) {
|
|
|
|
bonus = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
float heightDifference = origin->GetBounds().h - destination->GetBounds().h;
|
|
|
|
if (heightDifference == 0) {
|
|
|
|
bonus = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-23 13:55:11 +02:00
|
|
|
if (wrongDirection)
|
|
|
|
return 0.0f;
|
|
|
|
else
|
2013-12-01 15:36:18 +01:00
|
|
|
return 1.0f / distance + overlap*10 + bonus;
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
|
|
|
|
2013-05-27 00:54:02 +02:00
|
|
|
NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, NeighborResult result) {
|
2013-08-18 23:11:26 +02:00
|
|
|
if (!IsEnabled())
|
2013-06-08 22:41:17 +02:00
|
|
|
return result;
|
|
|
|
if (GetVisibility() != V_VISIBLE)
|
|
|
|
return result;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
// First, find the position of the view in the list.
|
2013-10-19 14:29:32 -07:00
|
|
|
int num = -1;
|
2013-05-03 00:21:39 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
if (views_[i] == view) {
|
2013-10-19 14:29:32 -07:00
|
|
|
num = (int)i;
|
2013-05-03 00:21:39 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Do the cardinal directions right. Now we just map to
|
|
|
|
// prev/next.
|
2013-10-23 13:55:11 +02:00
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
switch (direction) {
|
2013-05-27 00:54:02 +02:00
|
|
|
case FOCUS_PREV:
|
|
|
|
// If view not found, no neighbor to find.
|
|
|
|
if (num == -1)
|
|
|
|
return NeighborResult(0, 0.0f);
|
|
|
|
return NeighborResult(views_[(num + views_.size() - 1) % views_.size()], 0.0f);
|
|
|
|
|
|
|
|
case FOCUS_NEXT:
|
|
|
|
// If view not found, no neighbor to find.
|
|
|
|
if (num == -1)
|
|
|
|
return NeighborResult(0, 0.0f);
|
|
|
|
return NeighborResult(views_[(num + 1) % views_.size()], 0.0f);
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
case FOCUS_UP:
|
|
|
|
case FOCUS_LEFT:
|
|
|
|
case FOCUS_RIGHT:
|
|
|
|
case FOCUS_DOWN:
|
2013-05-27 00:54:02 +02:00
|
|
|
{
|
|
|
|
// First, try the child views themselves as candidates
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
if (views_[i] == view)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
float score = GetDirectionScore(view, views_[i], direction);
|
|
|
|
if (score > result.score) {
|
|
|
|
result.score = score;
|
|
|
|
result.view = views_[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then go right ahead and see if any of the children contain any better candidates.
|
|
|
|
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
2013-06-09 13:01:36 +02:00
|
|
|
if ((*iter)->IsViewGroup()) {
|
|
|
|
ViewGroup *vg = static_cast<ViewGroup *>(*iter);
|
|
|
|
if (vg)
|
|
|
|
result = vg->FindNeighbor(view, direction, result);
|
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Boost neighbors with the same parent
|
|
|
|
if (num != -1) {
|
2013-06-08 22:41:17 +02:00
|
|
|
//result.score += 100.0f;
|
2013-05-27 00:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
|
|
|
|
default:
|
2013-05-27 00:54:02 +02:00
|
|
|
return result;
|
2013-10-23 13:55:11 +02:00
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-27 00:54:02 +02:00
|
|
|
void MoveFocus(ViewGroup *root, FocusDirection direction) {
|
|
|
|
if (!GetFocusedView()) {
|
|
|
|
// Nothing was focused when we got in here. Focus the first non-group in the hierarchy.
|
|
|
|
root->SetFocus();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NeighborResult neigh(0, 0);
|
|
|
|
neigh = root->FindNeighbor(GetFocusedView(), direction, neigh);
|
|
|
|
|
|
|
|
if (neigh.view) {
|
|
|
|
neigh.view->SetFocus();
|
2013-05-27 21:39:56 +02:00
|
|
|
|
|
|
|
root->SubviewFocused(neigh.view);
|
|
|
|
|
|
|
|
//if (neigh.parent != 0) {
|
2013-05-27 00:54:02 +02:00
|
|
|
// Let scrollviews and similar know that a child has been focused.
|
2013-05-27 21:39:56 +02:00
|
|
|
//neigh.parent->SubviewFocused(neigh.view);
|
|
|
|
//}
|
2013-05-27 00:54:02 +02:00
|
|
|
}
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
2013-06-08 22:41:17 +02:00
|
|
|
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
|
|
|
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
|
|
|
|
|
|
|
if (views_.empty())
|
2013-10-23 13:55:11 +02:00
|
|
|
return;
|
2013-05-03 00:21:39 +02:00
|
|
|
|
|
|
|
float sum = 0.0f;
|
|
|
|
float maxOther = 0.0f;
|
|
|
|
float totalWeight = 0.0f;
|
|
|
|
float weightSum = 0.0f;
|
|
|
|
float weightZeroSum = 0.0f;
|
|
|
|
|
2013-06-02 23:44:28 +02:00
|
|
|
int numVisible = 0;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
2013-06-02 23:44:28 +02:00
|
|
|
if (views_[i]->GetVisibility() == V_GONE)
|
|
|
|
continue;
|
|
|
|
numVisible++;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
const LayoutParams *layoutParams = views_[i]->GetLayoutParams();
|
2013-06-09 13:01:36 +02:00
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams *>(layoutParams);
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
Margins margins = defaultMargins_;
|
|
|
|
|
|
|
|
if (linLayoutParams) {
|
|
|
|
totalWeight += linLayoutParams->weight;
|
|
|
|
if (linLayoutParams->HasMargins())
|
|
|
|
margins = linLayoutParams->margins;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
2013-06-08 22:41:17 +02:00
|
|
|
MeasureSpec v = vert;
|
|
|
|
if (v.type == UNSPECIFIED) v = MeasureSpec(AT_MOST, measuredHeight_);
|
|
|
|
views_[i]->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), vert - (float)(margins.top + margins.bottom));
|
2013-05-03 00:21:39 +02:00
|
|
|
} else if (orientation_ == ORIENT_VERTICAL) {
|
2013-06-08 22:41:17 +02:00
|
|
|
MeasureSpec h = horiz;
|
|
|
|
if (h.type == UNSPECIFIED) h = MeasureSpec(AT_MOST, measuredWidth_);
|
|
|
|
views_[i]->Measure(dc, h - (float)(margins.left + margins.right), MeasureSpec(UNSPECIFIED, measuredHeight_));
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
float amount;
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
|
|
|
amount = views_[i]->GetMeasuredWidth() + margins.left + margins.right;
|
|
|
|
maxOther = std::max(maxOther, views_[i]->GetMeasuredHeight() + margins.top + margins.bottom);
|
|
|
|
} else {
|
|
|
|
amount = views_[i]->GetMeasuredHeight() + margins.top + margins.bottom;
|
|
|
|
maxOther = std::max(maxOther, views_[i]->GetMeasuredWidth() + margins.left + margins.right);
|
|
|
|
}
|
|
|
|
|
|
|
|
sum += amount;
|
|
|
|
if (linLayoutParams) {
|
|
|
|
if (linLayoutParams->weight == 0.0f)
|
|
|
|
weightZeroSum += amount;
|
|
|
|
|
|
|
|
weightSum += linLayoutParams->weight;
|
|
|
|
} else {
|
|
|
|
weightZeroSum += amount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-02 23:44:28 +02:00
|
|
|
weightZeroSum += spacing_ * (numVisible - 1);
|
2013-05-03 00:21:39 +02:00
|
|
|
|
|
|
|
// Awright, got the sum. Let's take the remaining space after the fixed-size views,
|
|
|
|
// and distribute among the weighted ones.
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
2013-05-25 15:12:46 +02:00
|
|
|
MeasureBySpec(layoutParams_->width, weightZeroSum, horiz, &measuredWidth_);
|
2013-05-03 00:21:39 +02:00
|
|
|
MeasureBySpec(layoutParams_->height, maxOther, vert, &measuredHeight_);
|
|
|
|
|
|
|
|
float unit = (measuredWidth_ - weightZeroSum) / weightSum;
|
|
|
|
// Redistribute the stretchy ones! and remeasure the children!
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
const LayoutParams *layoutParams = views_[i]->GetLayoutParams();
|
2013-06-09 13:01:36 +02:00
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams *>(layoutParams);
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-08-16 16:47:25 +02:00
|
|
|
if (linLayoutParams && linLayoutParams->weight > 0.0f) {
|
|
|
|
int marginSum = linLayoutParams->margins.left + linLayoutParams->margins.right;
|
|
|
|
views_[i]->Measure(dc, MeasureSpec(EXACTLY, unit * linLayoutParams->weight - marginSum), MeasureSpec(EXACTLY, measuredHeight_));
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
} else {
|
2013-08-10 23:03:12 +02:00
|
|
|
//MeasureBySpec(layoutParams_->height, vert.type == UNSPECIFIED ? sum : weightZeroSum, vert, &measuredHeight_);
|
2013-05-25 15:12:46 +02:00
|
|
|
MeasureBySpec(layoutParams_->height, weightZeroSum, vert, &measuredHeight_);
|
2013-05-03 00:21:39 +02:00
|
|
|
MeasureBySpec(layoutParams_->width, maxOther, horiz, &measuredWidth_);
|
|
|
|
|
|
|
|
float unit = (measuredHeight_ - weightZeroSum) / weightSum;
|
|
|
|
|
|
|
|
// Redistribute! and remeasure children!
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
const LayoutParams *layoutParams = views_[i]->GetLayoutParams();
|
2013-06-09 13:01:36 +02:00
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams *>(layoutParams);
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-08-16 16:47:25 +02:00
|
|
|
if (linLayoutParams && linLayoutParams->weight > 0.0f) {
|
|
|
|
int marginSum = linLayoutParams->margins.top + linLayoutParams->margins.bottom;
|
|
|
|
views_[i]->Measure(dc, MeasureSpec(EXACTLY, measuredWidth_), MeasureSpec(EXACTLY, unit * linLayoutParams->weight - marginSum));
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Stretch and squeeze!
|
|
|
|
// weight != 0 = fill remaining space.
|
|
|
|
void LinearLayout::Layout() {
|
|
|
|
const Bounds &bounds = bounds_;
|
|
|
|
|
|
|
|
Bounds itemBounds;
|
|
|
|
float pos;
|
2013-12-06 16:44:39 +01:00
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
|
|
|
pos = bounds.x;
|
|
|
|
itemBounds.y = bounds.y;
|
|
|
|
itemBounds.h = measuredHeight_;
|
|
|
|
} else {
|
|
|
|
pos = bounds.y;
|
|
|
|
itemBounds.x = bounds.x;
|
|
|
|
itemBounds.w = measuredWidth_;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
2013-06-02 23:44:28 +02:00
|
|
|
if (views_[i]->GetVisibility() == V_GONE)
|
|
|
|
continue;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
const LayoutParams *layoutParams = views_[i]->GetLayoutParams();
|
2013-06-09 13:01:36 +02:00
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams *>(layoutParams);
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-12-06 16:44:39 +01:00
|
|
|
Gravity gravity = G_TOPLEFT;
|
2013-05-03 00:21:39 +02:00
|
|
|
Margins margins = defaultMargins_;
|
|
|
|
if (linLayoutParams) {
|
|
|
|
if (linLayoutParams->HasMargins())
|
|
|
|
margins = linLayoutParams->margins;
|
|
|
|
gravity = linLayoutParams->gravity;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
|
|
|
itemBounds.x = pos;
|
|
|
|
itemBounds.w = views_[i]->GetMeasuredWidth() + margins.left + margins.right;
|
|
|
|
} else {
|
|
|
|
itemBounds.y = pos;
|
|
|
|
itemBounds.h = views_[i]->GetMeasuredHeight() + margins.top + margins.bottom;
|
|
|
|
}
|
|
|
|
|
|
|
|
Bounds innerBounds;
|
|
|
|
ApplyGravity(itemBounds, margins,
|
|
|
|
views_[i]->GetMeasuredWidth(), views_[i]->GetMeasuredHeight(),
|
|
|
|
gravity, innerBounds);
|
|
|
|
|
|
|
|
views_[i]->SetBounds(innerBounds);
|
|
|
|
views_[i]->Layout();
|
|
|
|
|
|
|
|
pos += spacing_ + (orientation_ == ORIENT_HORIZONTAL ? itemBounds.w : itemBounds.h);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void FrameLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
2013-05-25 12:40:57 +02:00
|
|
|
if (views_.empty()) {
|
|
|
|
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
|
|
|
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
2013-12-06 16:44:39 +01:00
|
|
|
return;
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
views_[i]->Measure(dc, horiz, vert);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrameLayout::Layout() {
|
2013-06-09 11:18:57 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
float w = views_[i]->GetMeasuredWidth();
|
|
|
|
float h = views_[i]->GetMeasuredHeight();
|
|
|
|
|
|
|
|
Bounds bounds;
|
|
|
|
bounds.w = w;
|
|
|
|
bounds.h = h;
|
2013-05-25 12:40:57 +02:00
|
|
|
|
2013-06-09 11:18:57 +02:00
|
|
|
bounds.x = bounds_.x + (measuredWidth_ - w) / 2;
|
|
|
|
bounds.y = bounds_.y + (measuredWidth_ - h) / 2;
|
|
|
|
views_[i]->SetBounds(bounds);
|
|
|
|
}
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
2013-05-25 16:52:27 +02:00
|
|
|
// Respect margins
|
|
|
|
Margins margins;
|
2013-08-18 22:29:50 +02:00
|
|
|
if (views_.size()) {
|
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams*>(views_[0]->GetLayoutParams());
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
|
|
|
if (linLayoutParams) {
|
|
|
|
margins = linLayoutParams->margins;
|
|
|
|
}
|
2013-05-25 16:52:27 +02:00
|
|
|
}
|
|
|
|
|
2013-08-10 23:03:12 +02:00
|
|
|
// The scroll view itself simply obeys its parent - but also tries to fit the child if possible.
|
2013-05-25 12:40:57 +02:00
|
|
|
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
|
|
|
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
2013-05-25 15:12:46 +02:00
|
|
|
|
2013-08-18 22:29:50 +02:00
|
|
|
if (views_.size()) {
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL) {
|
|
|
|
views_[0]->Measure(dc, MeasureSpec(UNSPECIFIED), MeasureSpec(AT_MOST, measuredHeight_ - (margins.top + margins.bottom)));
|
|
|
|
} else {
|
|
|
|
views_[0]->Measure(dc, MeasureSpec(AT_MOST, measuredWidth_ - (margins.left + margins.right)), MeasureSpec(UNSPECIFIED));
|
|
|
|
}
|
|
|
|
if (orientation_ == ORIENT_VERTICAL && vert.type != EXACTLY && measuredHeight_ < views_[0]->GetBounds().h)
|
|
|
|
measuredHeight_ = views_[0]->GetBounds().h;
|
2013-05-25 15:12:46 +02:00
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ScrollView::Layout() {
|
2013-08-18 22:29:50 +02:00
|
|
|
if (!views_.size())
|
|
|
|
return;
|
2013-05-25 15:12:46 +02:00
|
|
|
Bounds scrolled;
|
2013-05-25 16:52:27 +02:00
|
|
|
|
|
|
|
// Respect margins
|
|
|
|
Margins margins;
|
2013-06-09 13:01:36 +02:00
|
|
|
const LinearLayoutParams *linLayoutParams = static_cast<const LinearLayoutParams*>(views_[0]->GetLayoutParams());
|
|
|
|
if (!linLayoutParams->Is(LP_LINEAR)) linLayoutParams = 0;
|
|
|
|
if (linLayoutParams) {
|
|
|
|
margins = linLayoutParams->margins;
|
2013-05-25 16:52:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scrolled.w = views_[0]->GetMeasuredWidth() - (margins.left + margins.right);
|
|
|
|
scrolled.h = views_[0]->GetMeasuredHeight() - (margins.top + margins.bottom);
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-08-18 22:29:50 +02:00
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
switch (orientation_) {
|
|
|
|
case ORIENT_HORIZONTAL:
|
2013-08-18 22:29:50 +02:00
|
|
|
if (scrolled.w != lastViewSize_) {
|
|
|
|
ScrollTo(0.0f);
|
|
|
|
lastViewSize_ = scrolled.w;
|
|
|
|
}
|
2013-05-25 15:12:46 +02:00
|
|
|
scrolled.x = bounds_.x - scrollPos_;
|
2013-05-25 16:52:27 +02:00
|
|
|
scrolled.y = bounds_.y + margins.top;
|
2013-05-03 00:21:39 +02:00
|
|
|
break;
|
|
|
|
case ORIENT_VERTICAL:
|
2013-08-19 22:05:22 +02:00
|
|
|
if (scrolled.h != lastViewSize_ && scrollToTopOnSizeChange_) {
|
2013-08-18 22:29:50 +02:00
|
|
|
ScrollTo(0.0f);
|
|
|
|
lastViewSize_ = scrolled.h;
|
|
|
|
}
|
2013-05-25 16:52:27 +02:00
|
|
|
scrolled.x = bounds_.x + margins.left;
|
2013-05-25 15:12:46 +02:00
|
|
|
scrolled.y = bounds_.y - scrollPos_;
|
2013-05-03 00:21:39 +02:00
|
|
|
break;
|
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
views_[0]->SetBounds(scrolled);
|
2013-05-25 15:12:46 +02:00
|
|
|
views_[0]->Layout();
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
2013-07-08 12:34:39 +02:00
|
|
|
void ScrollView::Key(const KeyInput &input) {
|
2013-08-20 16:31:47 +02:00
|
|
|
if (visibility_ != V_VISIBLE)
|
|
|
|
return ViewGroup::Key(input);
|
|
|
|
|
2013-08-12 23:07:27 +02:00
|
|
|
if (input.flags & KEY_DOWN) {
|
2013-07-08 12:34:39 +02:00
|
|
|
switch (input.keyCode) {
|
2013-08-05 03:30:34 +10:00
|
|
|
case NKCODE_EXT_MOUSEWHEEL_UP:
|
2013-07-08 12:34:39 +02:00
|
|
|
ScrollRelative(-250);
|
|
|
|
break;
|
2013-08-05 03:30:34 +10:00
|
|
|
case NKCODE_EXT_MOUSEWHEEL_DOWN:
|
2013-07-08 12:34:39 +02:00
|
|
|
ScrollRelative(250);
|
|
|
|
break;
|
2013-08-20 18:58:00 +02:00
|
|
|
case NKCODE_PAGE_DOWN:
|
|
|
|
ScrollRelative(bounds_.h - 50);
|
|
|
|
break;
|
|
|
|
case NKCODE_PAGE_UP:
|
|
|
|
ScrollRelative(-bounds_.h + 50);
|
|
|
|
break;
|
|
|
|
case NKCODE_MOVE_HOME:
|
|
|
|
ScrollTo(0);
|
|
|
|
break;
|
|
|
|
case NKCODE_MOVE_END:
|
|
|
|
if (views_.size())
|
|
|
|
ScrollTo(views_[0]->GetBounds().h);
|
|
|
|
break;
|
2013-07-08 12:34:39 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-12 23:07:27 +02:00
|
|
|
ViewGroup::Key(input);
|
2013-07-08 12:34:39 +02:00
|
|
|
}
|
|
|
|
|
2013-08-20 11:35:54 +02:00
|
|
|
const float friction = 0.92f;
|
|
|
|
const float stop_threshold = 0.1f;
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
void ScrollView::Touch(const TouchInput &input) {
|
|
|
|
if ((input.flags & TOUCH_DOWN) && input.id == 0) {
|
2013-05-25 15:12:46 +02:00
|
|
|
scrollStart_ = scrollPos_;
|
2013-08-20 11:35:54 +02:00
|
|
|
inertia_ = 0.0f;
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
2013-08-20 11:35:54 +02:00
|
|
|
if (input.flags & TOUCH_UP) {
|
|
|
|
float info[4];
|
2014-01-31 14:31:19 +01:00
|
|
|
if (!IsDragCaptured(input.id) && gesture_.GetGestureInfo(GESTURE_DRAG_VERTICAL, info))
|
2013-08-20 16:31:47 +02:00
|
|
|
inertia_ = info[1];
|
2013-08-20 11:35:54 +02:00
|
|
|
}
|
|
|
|
|
2013-08-10 23:03:12 +02:00
|
|
|
TouchInput input2;
|
2014-01-31 14:31:19 +01:00
|
|
|
if (CanScroll() && !IsDragCaptured(input.id)) {
|
2013-08-10 23:03:12 +02:00
|
|
|
input2 = gesture_.Update(input, bounds_);
|
2013-08-20 16:31:47 +02:00
|
|
|
float info[4];
|
|
|
|
if (gesture_.GetGestureInfo(GESTURE_DRAG_VERTICAL, info)) {
|
2013-08-10 23:03:12 +02:00
|
|
|
float pos = scrollStart_ - info[0];
|
|
|
|
ClampScrollPos(pos);
|
|
|
|
scrollPos_ = pos;
|
|
|
|
scrollTarget_ = pos;
|
|
|
|
scrollToTarget_ = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
input2 = input;
|
2013-05-25 15:12:46 +02:00
|
|
|
}
|
2013-08-10 23:03:12 +02:00
|
|
|
|
|
|
|
if (!(input.flags & TOUCH_DOWN) || bounds_.Contains(input.x, input.y)) {
|
2013-05-25 16:52:27 +02:00
|
|
|
ViewGroup::Touch(input2);
|
2013-08-10 23:03:12 +02:00
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void ScrollView::Draw(UIContext &dc) {
|
2013-08-18 22:29:50 +02:00
|
|
|
if (!views_.size()) {
|
|
|
|
ViewGroup::Draw(dc);
|
|
|
|
return;
|
|
|
|
}
|
2013-11-20 14:52:09 +01:00
|
|
|
|
2013-05-27 22:57:06 +02:00
|
|
|
dc.PushScissor(bounds_);
|
2013-05-25 15:12:46 +02:00
|
|
|
views_[0]->Draw(dc);
|
2013-05-27 22:57:06 +02:00
|
|
|
dc.PopScissor();
|
2013-07-16 00:25:08 +02:00
|
|
|
|
|
|
|
float childHeight = views_[0]->GetBounds().h;
|
|
|
|
float scrollMax = std::max(0.0f, childHeight - bounds_.h);
|
|
|
|
|
|
|
|
float ratio = bounds_.h / views_[0]->GetBounds().h;
|
2013-08-16 16:47:25 +02:00
|
|
|
|
|
|
|
float bobWidth = 5;
|
2013-07-16 00:25:08 +02:00
|
|
|
if (ratio < 1.0f && scrollMax > 0.0f) {
|
|
|
|
float bobHeight = ratio * bounds_.h;
|
|
|
|
float bobOffset = (scrollPos_ / scrollMax) * (bounds_.h - bobHeight);
|
|
|
|
|
2013-08-16 16:47:25 +02:00
|
|
|
Bounds bob(bounds_.x2() - bobWidth, bounds_.y + bobOffset, bobWidth, bobHeight);
|
2013-07-16 00:25:08 +02:00
|
|
|
dc.FillRect(Drawable(0x80FFFFFF), bob);
|
|
|
|
}
|
2013-05-25 15:12:46 +02:00
|
|
|
}
|
2013-05-25 12:40:57 +02:00
|
|
|
|
2013-05-27 21:39:56 +02:00
|
|
|
bool ScrollView::SubviewFocused(View *view) {
|
|
|
|
if (!ViewGroup::SubviewFocused(view))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const Bounds &vBounds = view->GetBounds();
|
|
|
|
|
|
|
|
// Scroll so that the focused view is visible.
|
|
|
|
switch (orientation_) {
|
|
|
|
case ORIENT_HORIZONTAL:
|
|
|
|
if (vBounds.x2() > bounds_.x2()) {
|
|
|
|
ScrollTo(scrollPos_ + vBounds.x2() - bounds_.x2());
|
|
|
|
}
|
|
|
|
if (vBounds.x < bounds_.x) {
|
|
|
|
ScrollTo(scrollPos_ + (vBounds.x - bounds_.x));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ORIENT_VERTICAL:
|
|
|
|
if (vBounds.y2() > bounds_.y2()) {
|
|
|
|
ScrollTo(scrollPos_ + vBounds.y2() - bounds_.y2());
|
|
|
|
}
|
|
|
|
if (vBounds.y < bounds_.y) {
|
2013-07-15 20:29:50 +02:00
|
|
|
ScrollTo(scrollPos_ + (vBounds.y - bounds_.y));
|
2013-05-27 21:39:56 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScrollView::ScrollTo(float newScrollPos) {
|
2013-05-28 00:50:19 +02:00
|
|
|
scrollTarget_ = newScrollPos;
|
|
|
|
scrollToTarget_ = true;
|
2013-07-10 21:58:35 +02:00
|
|
|
ClampScrollPos(scrollTarget_);
|
2013-07-08 12:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ScrollView::ScrollRelative(float distance) {
|
|
|
|
scrollTarget_ = scrollPos_ + distance;
|
|
|
|
scrollToTarget_ = true;
|
2013-07-10 21:58:35 +02:00
|
|
|
ClampScrollPos(scrollTarget_);
|
2013-05-28 00:50:19 +02:00
|
|
|
}
|
|
|
|
|
2013-07-10 21:58:35 +02:00
|
|
|
void ScrollView::ClampScrollPos(float &pos) {
|
2013-11-20 14:52:09 +01:00
|
|
|
if (!views_.size()) {
|
2013-08-18 22:29:50 +02:00
|
|
|
pos = 0.0f;
|
2013-11-20 14:52:09 +01:00
|
|
|
return;
|
|
|
|
}
|
2013-07-08 12:34:39 +02:00
|
|
|
// Clamp scrollTarget.
|
|
|
|
float childHeight = views_[0]->GetBounds().h;
|
|
|
|
float scrollMax = std::max(0.0f, childHeight - bounds_.h);
|
2013-07-10 21:58:35 +02:00
|
|
|
|
|
|
|
if (pos < 0.0f) {
|
|
|
|
pos = 0.0f;
|
|
|
|
}
|
|
|
|
if (pos > scrollMax) {
|
|
|
|
pos = scrollMax;
|
2013-07-08 12:34:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-10 23:03:12 +02:00
|
|
|
bool ScrollView::CanScroll() const {
|
2013-08-18 22:29:50 +02:00
|
|
|
if (!views_.size())
|
|
|
|
return false;
|
2013-08-10 23:03:12 +02:00
|
|
|
return views_[0]->GetBounds().h > bounds_.h;
|
|
|
|
}
|
2013-07-08 12:34:39 +02:00
|
|
|
|
2013-05-28 00:50:19 +02:00
|
|
|
void ScrollView::Update(const InputState &input_state) {
|
2013-08-20 15:30:47 +02:00
|
|
|
if (visibility_ != V_VISIBLE) {
|
|
|
|
inertia_ = 0.0f;
|
|
|
|
}
|
2013-05-28 00:50:19 +02:00
|
|
|
ViewGroup::Update(input_state);
|
2013-08-20 11:35:54 +02:00
|
|
|
gesture_.UpdateFrame();
|
2013-05-28 00:50:19 +02:00
|
|
|
if (scrollToTarget_) {
|
2013-08-20 11:35:54 +02:00
|
|
|
inertia_ = 0.0f;
|
2013-05-28 00:50:19 +02:00
|
|
|
if (fabsf(scrollTarget_ - scrollPos_) < 0.5f) {
|
|
|
|
scrollPos_ = scrollTarget_;
|
|
|
|
scrollToTarget_ = false;
|
|
|
|
} else {
|
|
|
|
scrollPos_ += (scrollTarget_ - scrollPos_) * 0.3f;
|
|
|
|
}
|
2013-08-20 11:35:54 +02:00
|
|
|
} else if (inertia_ != 0.0f && !gesture_.IsGestureActive(GESTURE_DRAG_VERTICAL)) {
|
|
|
|
scrollPos_ -= inertia_;
|
|
|
|
inertia_ *= friction;
|
|
|
|
if (fabsf(inertia_) < stop_threshold)
|
|
|
|
inertia_ = 0.0f;
|
|
|
|
ClampScrollPos(scrollPos_);
|
2013-05-28 00:50:19 +02:00
|
|
|
}
|
2013-05-27 00:54:02 +02:00
|
|
|
}
|
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void AnchorLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
2013-05-27 22:22:35 +02:00
|
|
|
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
2013-06-08 22:41:17 +02:00
|
|
|
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
2013-05-27 22:22:35 +02:00
|
|
|
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
Size width = WRAP_CONTENT;
|
|
|
|
Size height = WRAP_CONTENT;
|
|
|
|
|
|
|
|
MeasureSpec specW(UNSPECIFIED, 0.0f);
|
|
|
|
MeasureSpec specH(UNSPECIFIED, 0.0f);
|
|
|
|
|
2013-06-09 13:01:36 +02:00
|
|
|
const AnchorLayoutParams *params = static_cast<const AnchorLayoutParams *>(views_[i]->GetLayoutParams());
|
|
|
|
if (!params->Is(LP_ANCHOR)) params = 0;
|
2013-05-27 22:22:35 +02:00
|
|
|
if (params) {
|
|
|
|
width = params->width;
|
|
|
|
height = params->height;
|
|
|
|
|
2013-07-20 12:04:24 +02:00
|
|
|
if (!params->center) {
|
|
|
|
if (params->left >= 0 && params->right >= 0) {
|
|
|
|
width = measuredWidth_ - params->left - params->right;
|
|
|
|
}
|
|
|
|
if (params->top >= 0 && params->bottom >= 0) {
|
|
|
|
height = measuredHeight_ - params->top - params->bottom;
|
|
|
|
}
|
2013-05-27 22:22:35 +02:00
|
|
|
}
|
2013-06-09 11:18:57 +02:00
|
|
|
specW = width < 0 ? MeasureSpec(UNSPECIFIED) : MeasureSpec(EXACTLY, width);
|
|
|
|
specH = height < 0 ? MeasureSpec(UNSPECIFIED) : MeasureSpec(EXACTLY, height);
|
2013-05-27 22:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
views_[i]->Measure(dc, specW, specH);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnchorLayout::Layout() {
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
2013-06-09 13:01:36 +02:00
|
|
|
const AnchorLayoutParams *params = static_cast<const AnchorLayoutParams *>(views_[i]->GetLayoutParams());
|
|
|
|
if (!params->Is(LP_ANCHOR)) params = 0;
|
2013-05-27 22:22:35 +02:00
|
|
|
|
|
|
|
Bounds vBounds;
|
|
|
|
vBounds.w = views_[i]->GetMeasuredWidth();
|
|
|
|
vBounds.h = views_[i]->GetMeasuredHeight();
|
|
|
|
|
2013-08-10 23:03:12 +02:00
|
|
|
// Clamp width/height to our own
|
|
|
|
if (vBounds.w > bounds_.w) vBounds.w = bounds_.w;
|
|
|
|
if (vBounds.h > bounds_.h) vBounds.h = bounds_.h;
|
|
|
|
|
2013-07-20 12:04:24 +02:00
|
|
|
float left = 0, top = 0, right = 0, bottom = 0, center = false;
|
2013-05-27 22:22:35 +02:00
|
|
|
if (params) {
|
|
|
|
left = params->left;
|
|
|
|
top = params->top;
|
|
|
|
right = params->right;
|
|
|
|
bottom = params->bottom;
|
2013-07-20 12:04:24 +02:00
|
|
|
center = params->center;
|
2013-05-27 22:22:35 +02:00
|
|
|
}
|
|
|
|
|
2013-07-20 12:04:24 +02:00
|
|
|
if (left >= 0) {
|
2013-06-09 11:18:57 +02:00
|
|
|
vBounds.x = bounds_.x + left;
|
2013-07-20 12:04:24 +02:00
|
|
|
if (center)
|
|
|
|
vBounds.x -= vBounds.w * 0.5f;
|
|
|
|
} else if (right >= 0) {
|
2013-06-09 11:18:57 +02:00
|
|
|
vBounds.x = bounds_.x2() - right - vBounds.w;
|
2013-07-20 12:04:24 +02:00
|
|
|
if (center) {
|
|
|
|
vBounds.x += vBounds.w * 0.5f;
|
|
|
|
}
|
|
|
|
}
|
2013-06-09 11:18:57 +02:00
|
|
|
|
2013-07-20 12:04:24 +02:00
|
|
|
if (top >= 0) {
|
2013-06-09 11:18:57 +02:00
|
|
|
vBounds.y = bounds_.y + top;
|
2013-07-20 12:04:24 +02:00
|
|
|
if (center)
|
|
|
|
vBounds.y -= vBounds.h * 0.5f;
|
|
|
|
} else if (bottom >= 0) {
|
2013-06-09 11:18:57 +02:00
|
|
|
vBounds.y = bounds_.y2() - bottom - vBounds.h;
|
2013-07-20 12:04:24 +02:00
|
|
|
if (center)
|
|
|
|
vBounds.y += vBounds.h * 0.5f;
|
|
|
|
}
|
2013-05-27 22:22:35 +02:00
|
|
|
|
|
|
|
views_[i]->SetBounds(vBounds);
|
2013-06-09 11:18:57 +02:00
|
|
|
views_[i]->Layout();
|
2013-05-27 22:22:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-18 16:56:47 +02:00
|
|
|
void GridLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
|
|
|
MeasureSpecType measureType = settings_.fillCells ? EXACTLY : AT_MOST;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
views_[i]->Measure(dc, MeasureSpec(measureType, settings_.columnWidth), MeasureSpec(measureType, settings_.rowHeight));
|
|
|
|
}
|
|
|
|
|
|
|
|
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
|
|
|
|
|
|
|
// Okay, got the width we are supposed to adjust to. Now we can calculate the number of columns.
|
|
|
|
numColumns_ = (measuredWidth_ - settings_.spacing) / (settings_.columnWidth + settings_.spacing);
|
|
|
|
if (!numColumns_) numColumns_ = 1;
|
|
|
|
int numRows = (int)(views_.size() + (numColumns_ - 1)) / numColumns_;
|
|
|
|
|
|
|
|
float estimatedHeight = (settings_.rowHeight + settings_.spacing) * numRows;
|
|
|
|
|
|
|
|
MeasureBySpec(layoutParams_->height, estimatedHeight, vert, &measuredHeight_);
|
|
|
|
}
|
|
|
|
|
2013-05-03 00:21:39 +02:00
|
|
|
void GridLayout::Layout() {
|
2013-05-27 00:54:02 +02:00
|
|
|
int y = 0;
|
|
|
|
int x = 0;
|
2013-07-18 16:56:47 +02:00
|
|
|
int count = 0;
|
2013-05-27 00:54:02 +02:00
|
|
|
for (size_t i = 0; i < views_.size(); i++) {
|
|
|
|
Bounds itemBounds, innerBounds;
|
|
|
|
|
|
|
|
itemBounds.x = bounds_.x + x;
|
|
|
|
itemBounds.y = bounds_.y + y;
|
|
|
|
itemBounds.w = settings_.columnWidth;
|
|
|
|
itemBounds.h = settings_.rowHeight;
|
|
|
|
|
|
|
|
ApplyGravity(itemBounds, Margins(0.0f),
|
|
|
|
views_[i]->GetMeasuredWidth(), views_[i]->GetMeasuredHeight(),
|
|
|
|
G_HCENTER | G_VCENTER, innerBounds);
|
2013-05-03 00:21:39 +02:00
|
|
|
|
2013-05-27 00:54:02 +02:00
|
|
|
views_[i]->SetBounds(innerBounds);
|
|
|
|
views_[i]->Layout();
|
|
|
|
|
2013-07-18 16:56:47 +02:00
|
|
|
count++;
|
|
|
|
if (count == numColumns_) {
|
|
|
|
count = 0;
|
2013-05-27 00:54:02 +02:00
|
|
|
x = 0;
|
|
|
|
y += itemBounds.h + settings_.spacing;
|
|
|
|
} else {
|
2013-07-18 16:56:47 +02:00
|
|
|
x += itemBounds.w + settings_.spacing;
|
2013-05-27 00:54:02 +02:00
|
|
|
}
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
}
|
|
|
|
|
2013-06-08 22:41:17 +02:00
|
|
|
EventReturn TabHolder::OnTabClick(EventParams &e) {
|
|
|
|
tabs_[currentTab_]->SetVisibility(V_GONE);
|
2013-07-15 19:56:36 +02:00
|
|
|
currentTab_ = e.a;
|
2013-06-08 22:41:17 +02:00
|
|
|
tabs_[currentTab_]->SetVisibility(V_VISIBLE);
|
|
|
|
return EVENT_DONE;
|
|
|
|
}
|
|
|
|
|
2013-12-06 16:44:39 +01:00
|
|
|
ChoiceStrip::ChoiceStrip(Orientation orientation, LayoutParams *layoutParams)
|
|
|
|
: LinearLayout(orientation, layoutParams), selected_(0), topTabs_(false) {
|
|
|
|
SetSpacing(0.0f);
|
|
|
|
}
|
|
|
|
|
2013-07-15 19:56:36 +02:00
|
|
|
void ChoiceStrip::AddChoice(const std::string &title) {
|
2013-12-06 16:44:39 +01:00
|
|
|
StickyChoice *c = new StickyChoice(title, "",
|
|
|
|
orientation_ == ORIENT_HORIZONTAL ?
|
|
|
|
0 :
|
|
|
|
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
|
2013-07-15 19:56:36 +02:00
|
|
|
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
|
|
|
|
Add(c);
|
2013-07-16 00:25:08 +02:00
|
|
|
if (selected_ == (int)views_.size() - 1)
|
2013-07-15 19:56:36 +02:00
|
|
|
c->Press();
|
|
|
|
}
|
|
|
|
|
2013-08-20 00:34:54 +02:00
|
|
|
void ChoiceStrip::AddChoice(ImageID buttonImage) {
|
2013-12-06 16:44:39 +01:00
|
|
|
StickyChoice *c = new StickyChoice(buttonImage,
|
|
|
|
orientation_ == ORIENT_HORIZONTAL ?
|
|
|
|
0 :
|
|
|
|
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
|
2013-08-20 00:34:54 +02:00
|
|
|
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
|
|
|
|
Add(c);
|
|
|
|
if (selected_ == (int)views_.size() - 1)
|
|
|
|
c->Press();
|
|
|
|
}
|
|
|
|
|
2013-07-15 19:56:36 +02:00
|
|
|
EventReturn ChoiceStrip::OnChoiceClick(EventParams &e) {
|
|
|
|
// Unstick the other choices that weren't clicked.
|
2013-07-16 00:25:08 +02:00
|
|
|
for (int i = 0; i < (int)views_.size(); i++) {
|
|
|
|
if (views_[i] != e.v) {
|
2013-07-15 19:56:36 +02:00
|
|
|
static_cast<StickyChoice *>(views_[i])->Release();
|
2013-07-16 00:25:08 +02:00
|
|
|
} else {
|
2013-07-15 19:56:36 +02:00
|
|
|
selected_ = i;
|
2013-07-16 00:25:08 +02:00
|
|
|
}
|
2013-07-15 19:56:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
EventParams e2;
|
2013-07-20 09:42:44 +02:00
|
|
|
e2.v = views_[selected_];
|
|
|
|
e2.a = selected_;
|
2013-07-15 19:56:36 +02:00
|
|
|
// Dispatch immediately (we're already on the UI thread as we're in an event handler).
|
2013-07-20 09:42:44 +02:00
|
|
|
return OnChoice.Dispatch(e2);
|
2013-07-15 19:56:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChoiceStrip::SetSelection(int sel) {
|
2013-08-17 13:20:28 +02:00
|
|
|
int prevSelected = selected_;
|
2013-08-27 19:43:40 +02:00
|
|
|
if (selected_ < (int)views_.size())
|
2013-08-17 13:20:28 +02:00
|
|
|
static_cast<StickyChoice *>(views_[selected_])->Release();
|
|
|
|
selected_ = sel;
|
2013-08-27 19:43:40 +02:00
|
|
|
if (selected_ < (int)views_.size())
|
2013-08-17 13:20:28 +02:00
|
|
|
static_cast<StickyChoice *>(views_[selected_])->Press();
|
|
|
|
if (topTabs_ && prevSelected != selected_) {
|
2013-12-06 16:44:39 +01:00
|
|
|
EventParams e;
|
2013-08-17 13:20:28 +02:00
|
|
|
e.v = views_[selected_];
|
|
|
|
static_cast<StickyChoice *>(views_[selected_])->OnClick.Trigger(e);
|
2013-08-17 12:06:08 +02:00
|
|
|
}
|
2013-07-15 19:56:36 +02:00
|
|
|
}
|
|
|
|
|
2013-10-08 18:04:56 +05:30
|
|
|
void ChoiceStrip::HighlightChoice(unsigned int choice){
|
2013-10-19 14:29:32 -07:00
|
|
|
if (choice < (unsigned int)views_.size()){
|
2013-10-07 21:45:25 +05:30
|
|
|
static_cast<StickyChoice *>(views_[choice])->HighlightChanged(true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-13 00:06:23 +02:00
|
|
|
void ChoiceStrip::Key(const KeyInput &input) {
|
|
|
|
if (input.flags & KEY_DOWN) {
|
2014-01-25 11:58:49 -08:00
|
|
|
if (IsTabLeftKeyCode(input.keyCode) && selected_ > 0) {
|
2013-08-13 00:06:23 +02:00
|
|
|
SetSelection(selected_ - 1);
|
2014-01-25 11:58:49 -08:00
|
|
|
} else if (IsTabRightKeyCode(input.keyCode) && selected_ < (int)views_.size() - 1) {
|
2013-08-13 00:06:23 +02:00
|
|
|
SetSelection(selected_ + 1);
|
|
|
|
}
|
|
|
|
}
|
2013-08-16 16:47:25 +02:00
|
|
|
ViewGroup::Key(input);
|
2013-08-13 00:06:23 +02:00
|
|
|
}
|
|
|
|
|
2013-08-20 12:27:42 +02:00
|
|
|
void ChoiceStrip::Draw(UIContext &dc) {
|
|
|
|
ViewGroup::Draw(dc);
|
|
|
|
if (topTabs_) {
|
|
|
|
if (orientation_ == ORIENT_HORIZONTAL)
|
|
|
|
dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 4, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color );
|
|
|
|
else if (orientation_ == ORIENT_VERTICAL)
|
|
|
|
dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x2() - 4, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-15 19:56:36 +02:00
|
|
|
ListView::ListView(ListAdaptor *a, LayoutParams *layoutParams)
|
2013-12-11 09:32:14 +01:00
|
|
|
: ScrollView(ORIENT_VERTICAL, layoutParams), adaptor_(a), maxHeight_(0) {
|
|
|
|
|
2013-07-16 00:25:08 +02:00
|
|
|
linLayout_ = new LinearLayout(ORIENT_VERTICAL);
|
2013-07-17 01:03:29 +02:00
|
|
|
linLayout_->SetSpacing(0.0f);
|
2013-07-16 00:25:08 +02:00
|
|
|
Add(linLayout_);
|
|
|
|
CreateAllItems();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListView::CreateAllItems() {
|
|
|
|
linLayout_->Clear();
|
2013-07-15 19:56:36 +02:00
|
|
|
// Let's not be clever yet, we'll just create them all up front and add them all in.
|
|
|
|
for (int i = 0; i < adaptor_->GetNumItems(); i++) {
|
2013-07-16 00:25:08 +02:00
|
|
|
View * v = linLayout_->Add(adaptor_->CreateItemView(i));
|
2013-07-15 19:56:36 +02:00
|
|
|
adaptor_->AddEventCallback(v, std::bind(&ListView::OnItemCallback, this, i, placeholder::_1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-11 09:32:14 +01:00
|
|
|
void ListView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
|
|
|
ScrollView::Measure(dc, horiz, vert);
|
|
|
|
if (maxHeight_ > 0 && measuredHeight_ > maxHeight_) {
|
|
|
|
measuredHeight_ = maxHeight_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-15 19:56:36 +02:00
|
|
|
EventReturn ListView::OnItemCallback(int num, EventParams &e) {
|
|
|
|
EventParams ev;
|
2013-07-16 00:25:08 +02:00
|
|
|
ev.v = 0;
|
2013-07-15 19:56:36 +02:00
|
|
|
ev.a = num;
|
2013-07-16 00:25:08 +02:00
|
|
|
adaptor_->SetSelected(num);
|
|
|
|
View *focused = GetFocusedView();
|
2013-07-15 19:56:36 +02:00
|
|
|
OnChoice.Trigger(ev);
|
2013-07-16 00:25:08 +02:00
|
|
|
CreateAllItems();
|
|
|
|
if (focused)
|
|
|
|
SetFocusedView(linLayout_->GetViewByIndex(num));
|
2013-07-15 19:56:36 +02:00
|
|
|
return EVENT_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
View *ChoiceListAdaptor::CreateItemView(int index) {
|
|
|
|
return new Choice(items_[index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChoiceListAdaptor::AddEventCallback(View *view, std::function<EventReturn(EventParams&)> callback) {
|
|
|
|
Choice *choice = (Choice *)view;
|
|
|
|
choice->OnClick.Add(callback);
|
|
|
|
return EVENT_DONE;
|
|
|
|
}
|
|
|
|
|
2013-07-16 00:25:08 +02:00
|
|
|
|
|
|
|
View *StringVectorListAdaptor::CreateItemView(int index) {
|
|
|
|
return new Choice(items_[index], "", index == selected_);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StringVectorListAdaptor::AddEventCallback(View *view, std::function<EventReturn(EventParams&)> callback) {
|
|
|
|
Choice *choice = (Choice *)view;
|
|
|
|
choice->OnClick.Add(callback);
|
|
|
|
return EVENT_DONE;
|
|
|
|
}
|
|
|
|
|
2013-05-28 00:32:00 +02:00
|
|
|
void LayoutViewHierarchy(const UIContext &dc, ViewGroup *root) {
|
2013-07-20 12:04:24 +02:00
|
|
|
if (!root) {
|
|
|
|
ELOG("Tried to layout a view hierarchy from a zero pointer root");
|
|
|
|
return;
|
|
|
|
}
|
2013-05-03 00:21:39 +02:00
|
|
|
Bounds rootBounds;
|
|
|
|
rootBounds.x = 0;
|
|
|
|
rootBounds.y = 0;
|
|
|
|
rootBounds.w = dp_xres;
|
|
|
|
rootBounds.h = dp_yres;
|
|
|
|
|
|
|
|
MeasureSpec horiz(EXACTLY, rootBounds.w);
|
|
|
|
MeasureSpec vert(EXACTLY, rootBounds.h);
|
|
|
|
|
|
|
|
// Two phases - measure contents, layout.
|
|
|
|
root->Measure(dc, horiz, vert);
|
|
|
|
// Root has a specified size. Set it, then let root layout all its children.
|
|
|
|
root->SetBounds(rootBounds);
|
|
|
|
root->Layout();
|
|
|
|
}
|
|
|
|
|
2013-12-03 16:47:15 +01:00
|
|
|
// TODO: Figure out where this should really live.
|
|
|
|
// Simple simulation of key repeat on platforms and for gamepads where we don't
|
|
|
|
// automatically get it.
|
|
|
|
|
|
|
|
static int frameCount;
|
|
|
|
|
2014-01-05 12:49:40 +01:00
|
|
|
// Ignore deviceId when checking for matches. Turns out that Ouya for example sends
|
|
|
|
// completely broken input where the original keypresses have deviceId = 10 and the repeats
|
|
|
|
// have deviceId = 0.
|
2013-12-03 16:47:15 +01:00
|
|
|
struct HeldKey {
|
|
|
|
int key;
|
|
|
|
int deviceId;
|
|
|
|
int startFrame;
|
|
|
|
|
2014-01-03 14:21:24 +01:00
|
|
|
// Ignores startFrame
|
2013-12-03 16:47:15 +01:00
|
|
|
bool operator <(const HeldKey &other) const {
|
|
|
|
if (key < other.key) return true;
|
|
|
|
return false;
|
|
|
|
}
|
2014-01-05 12:49:40 +01:00
|
|
|
bool operator ==(const HeldKey &other) const { return key == other.key; }
|
2013-12-03 16:47:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static std::set<HeldKey> heldKeys;
|
|
|
|
|
|
|
|
static const int repeatDelay = 15; // 250ms
|
|
|
|
static const int repeatInterval = 5; // 66ms
|
|
|
|
|
2013-08-12 23:07:27 +02:00
|
|
|
void KeyEvent(const KeyInput &key, ViewGroup *root) {
|
|
|
|
if (key.flags & KEY_DOWN) {
|
|
|
|
// We ignore the device ID here. Anything with a DPAD is OK.
|
|
|
|
if (key.keyCode >= NKCODE_DPAD_UP && key.keyCode <= NKCODE_DPAD_RIGHT) {
|
2013-12-03 16:47:15 +01:00
|
|
|
// Let's only repeat DPAD initially.
|
|
|
|
HeldKey hk;
|
|
|
|
hk.key = key.keyCode;
|
|
|
|
hk.deviceId = key.deviceId;
|
|
|
|
hk.startFrame = frameCount;
|
2014-01-03 14:21:24 +01:00
|
|
|
|
|
|
|
// Check if the key is already held. If it is, ignore it. This is to avoid
|
|
|
|
// multiple key repeat mechanisms colliding.
|
|
|
|
if (heldKeys.find(hk) != heldKeys.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 16:47:15 +01:00
|
|
|
heldKeys.insert(hk);
|
2014-01-03 14:21:24 +01:00
|
|
|
lock_guard lock(focusLock);
|
|
|
|
focusMoves.push_back(key.keyCode);
|
2013-12-03 16:47:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (key.flags & KEY_UP) {
|
|
|
|
// We ignore the device ID here. Anything with a DPAD is OK.
|
|
|
|
if (key.keyCode >= NKCODE_DPAD_UP && key.keyCode <= NKCODE_DPAD_RIGHT) {
|
|
|
|
HeldKey hk;
|
|
|
|
hk.key = key.keyCode;
|
|
|
|
hk.deviceId = key.deviceId;
|
|
|
|
hk.startFrame = 0; // irrelevant
|
|
|
|
heldKeys.erase(hk);
|
2013-08-12 23:07:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
root->Key(key);
|
|
|
|
}
|
|
|
|
|
2013-12-03 16:47:15 +01:00
|
|
|
static void ProcessHeldKeys(ViewGroup *root) {
|
|
|
|
for (auto iter = heldKeys.begin(); iter != heldKeys.end(); ++iter) {
|
|
|
|
if (iter->startFrame < frameCount - repeatDelay) {
|
|
|
|
int frame = frameCount - (iter->startFrame + repeatDelay);
|
|
|
|
if ((frame % repeatInterval) == 0) {
|
|
|
|
KeyInput key;
|
|
|
|
key.keyCode = iter->key;
|
|
|
|
key.deviceId = iter->deviceId;
|
|
|
|
key.flags = KEY_DOWN;
|
|
|
|
KeyEvent(key, root);
|
2014-01-03 14:21:24 +01:00
|
|
|
|
|
|
|
lock_guard lock(focusLock);
|
|
|
|
focusMoves.push_back(key.keyCode);
|
2013-12-03 16:47:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-12 23:07:27 +02:00
|
|
|
void TouchEvent(const TouchInput &touch, ViewGroup *root) {
|
|
|
|
EnableFocusMovement(false);
|
|
|
|
|
|
|
|
root->Touch(touch);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AxisEvent(const AxisInput &axis, ViewGroup *root) {
|
|
|
|
root->Axis(axis);
|
|
|
|
}
|
|
|
|
|
2013-05-25 12:40:57 +02:00
|
|
|
void UpdateViewHierarchy(const InputState &input_state, ViewGroup *root) {
|
2013-12-03 16:47:15 +01:00
|
|
|
ProcessHeldKeys(root);
|
|
|
|
frameCount++;
|
|
|
|
|
2013-07-20 12:04:24 +02:00
|
|
|
if (!root) {
|
|
|
|
ELOG("Tried to update a view hierarchy from a zero pointer root");
|
|
|
|
return;
|
|
|
|
}
|
2013-08-12 23:07:27 +02:00
|
|
|
|
|
|
|
if (focusMoves.size()) {
|
|
|
|
lock_guard lock(focusLock);
|
2013-05-25 12:40:57 +02:00
|
|
|
EnableFocusMovement(true);
|
2013-05-28 00:50:19 +02:00
|
|
|
if (!GetFocusedView()) {
|
|
|
|
root->SetFocus();
|
|
|
|
root->SubviewFocused(GetFocusedView());
|
|
|
|
} else {
|
2013-08-12 23:07:27 +02:00
|
|
|
for (size_t i = 0; i < focusMoves.size(); i++) {
|
|
|
|
switch (focusMoves[i]) {
|
|
|
|
case NKCODE_DPAD_LEFT: MoveFocus(root, FOCUS_LEFT); break;
|
|
|
|
case NKCODE_DPAD_RIGHT: MoveFocus(root, FOCUS_RIGHT); break;
|
|
|
|
case NKCODE_DPAD_UP: MoveFocus(root, FOCUS_UP); break;
|
|
|
|
case NKCODE_DPAD_DOWN: MoveFocus(root, FOCUS_DOWN); break;
|
|
|
|
}
|
|
|
|
}
|
2013-05-28 00:50:19 +02:00
|
|
|
}
|
2013-08-12 23:07:27 +02:00
|
|
|
focusMoves.clear();
|
2013-05-28 00:50:19 +02:00
|
|
|
}
|
2013-05-25 12:40:57 +02:00
|
|
|
|
|
|
|
root->Update(input_state);
|
2013-06-27 16:20:18 +02:00
|
|
|
DispatchEvents();
|
2013-05-25 12:40:57 +02:00
|
|
|
}
|
2013-08-12 23:07:27 +02:00
|
|
|
|
2013-07-20 09:42:44 +02:00
|
|
|
} // namespace UI
|