#include "base/display.h" #include "base/logging.h" #include "ui/view.h" #include "ui/viewgroup.h" namespace UI { 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. for (auto iter = views_.begin(); iter != views_.end(); ++iter) delete *iter; } void ViewGroup::Touch(const TouchInput &input) { for (auto iter = views_.begin(); iter != views_.end(); ++iter) { // TODO: If there is a transformation active, transform input coordinates accordingly. (*iter)->Touch(input); } } void ViewGroup::Draw(DrawContext &dc) { for (auto iter = views_.begin(); iter != views_.end(); ++iter) { // TODO: If there is a transformation active, transform input coordinates accordingly. (*iter)->Draw(dc); } } 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. (*iter)->Update(input_state); } } bool ViewGroup::SetFocus() { if (!CanBeFocused() && !views_.empty()) { for (size_t i = 0; i < views_.size(); i++) { if (views_[i]->SetFocus()) return true; } } return false; } void ViewGroup::MoveFocus(FocusDirection direction) { if (!GetFocusedView()) { SetFocus(); return; } View *neighbor = FindNeighbor(GetFocusedView(), direction); if (neighbor) { neighbor->SetFocus(); } else { for (auto iter = views_.begin(); iter != views_.end(); ++iter) { (*iter)->MoveFocus(direction); } } } View *ViewGroup::FindNeighbor(View *view, FocusDirection direction) { // First, find the position of the view in the list. size_t num = -1; for (size_t i = 0; i < views_.size(); i++) { if (views_[i] == view) { num = i; break; } } // If view not found, no neighbor to find. if (num == -1) return 0; // TODO: Do the cardinal directions right. Now we just map to // prev/next. switch (direction) { case FOCUS_UP: case FOCUS_LEFT: case FOCUS_PREV: return views_[(num + views_.size() - 1) % views_.size()]; case FOCUS_RIGHT: case FOCUS_DOWN: case FOCUS_NEXT: return views_[(num + 1) % views_.size()]; default: return view; } } void LinearLayout::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) { if (views_.empty()) { MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_); MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_); return; } float sum = 0.0f; float maxOther = 0.0f; float totalWeight = 0.0f; float weightSum = 0.0f; float weightZeroSum = 0.0f; for (size_t i = 0; i < views_.size(); i++) { const LayoutParams *layoutParams = views_[i]->GetLayoutParams(); const LinearLayoutParams *linLayoutParams = dynamic_cast(layoutParams); Margins margins = defaultMargins_; if (linLayoutParams) { totalWeight += linLayoutParams->weight; if (linLayoutParams->HasMargins()) margins = linLayoutParams->margins; } if (orientation_ == ORIENT_HORIZONTAL) { views_[i]->Measure(dc, MeasureSpec(UNSPECIFIED), vert - (float)(margins.top + margins.bottom)); } else if (orientation_ == ORIENT_VERTICAL) { views_[i]->Measure(dc, horiz - (float)(margins.left + margins.right), MeasureSpec(UNSPECIFIED)); } 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; } } weightZeroSum += spacing_ * (views_.size() - 1); // 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) { MeasureBySpec(layoutParams_->width, sum, horiz, &measuredWidth_); 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(); const LinearLayoutParams *linLayoutParams = dynamic_cast(layoutParams); if (linLayoutParams && linLayoutParams->weight > 0.0) views_[i]->Measure(dc, MeasureSpec(EXACTLY, unit * linLayoutParams->weight), vert); } } else { MeasureBySpec(layoutParams_->height, sum, vert, &measuredHeight_); 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(); const LinearLayoutParams *linLayoutParams = dynamic_cast(layoutParams); if (linLayoutParams && linLayoutParams->weight) views_[i]->Measure(dc, horiz, MeasureSpec(EXACTLY, unit * linLayoutParams->weight)); } } } // TODO: Stretch and squeeze! // weight != 0 = fill remaining space. void LinearLayout::Layout() { const Bounds &bounds = bounds_; Bounds itemBounds; float pos; 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++) { const LayoutParams *layoutParams = views_[i]->GetLayoutParams(); const LinearLayoutParams *linLayoutParams = dynamic_cast(layoutParams); Gravity gravity = G_TOPLEFT; 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); } } void FrameLayout::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) { if (views_.empty()) { MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_); MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_); return; } for (size_t i = 0; i < views_.size(); i++) { views_[i]->Measure(dc, horiz, vert); } } void FrameLayout::Layout() { } void ScrollView::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) { // The scroll view itself simply obeys its parent. MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_); MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_); } void ScrollView::Layout() { Bounds scrolled = bounds_; switch (orientation_) { case ORIENT_HORIZONTAL: scrolled.x -= scrollPos_; break; case ORIENT_VERTICAL: scrolled.y -= scrollPos_; break; } views_[0]->SetBounds(scrolled); } void ScrollView::Touch(const TouchInput &input) { if ((input.flags & TOUCH_DOWN) && input.id == 0) { scrollStart_ = orientation_ == ORIENT_HORIZONTAL ? input.x : input.y; } gesture_.Update(input); if (gesture_.IsGestureActive(GESTURE_DRAG_VERTICAL)) { float info[4]; gesture_.GetGestureInfo(GESTURE_DRAG_VERTICAL, info); } } void GridLayout::Layout() { } void RelativeLayout::Layout() { } void LayoutViewHierarchy(const DrawContext &dc, ViewGroup *root) { 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(); } void UpdateViewHierarchy(const InputState &input_state, ViewGroup *root) { if (input_state.pad_buttons_down & (PAD_BUTTON_LEFT | PAD_BUTTON_RIGHT | PAD_BUTTON_UP | PAD_BUTTON_DOWN)) EnableFocusMovement(true); if (input_state.pad_last_buttons == 0) { if (input_state.pad_buttons_down & PAD_BUTTON_RIGHT) root->MoveFocus(FOCUS_RIGHT); if (input_state.pad_buttons_down & PAD_BUTTON_UP) root->MoveFocus(FOCUS_UP); if (input_state.pad_buttons_down & PAD_BUTTON_LEFT) root->MoveFocus(FOCUS_LEFT); if (input_state.pad_buttons_down & PAD_BUTTON_DOWN) root->MoveFocus(FOCUS_DOWN); } root->Update(input_state); } } // namespace UI