1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/panels/panel_scroller.h"
6
7#include "base/compiler_specific.h"
8#include "base/logging.h"
9#include "base/stl_util-inl.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/chromeos/panels/panel_scroller_container.h"
13#include "chrome/browser/chromeos/panels/panel_scroller_header.h"
14#include "ui/gfx/canvas.h"
15#include "views/widget/widget.h"
16
17struct PanelScroller::Panel {
18  PanelScrollerHeader* header;
19  PanelScrollerContainer* container;
20};
21
22PanelScroller::PanelScroller()
23    : views::View(),
24      divider_height_(18),
25      needs_layout_(true),
26      scroll_pos_(0),
27      ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)),
28      animated_scroll_begin_(0),
29      animated_scroll_end_(0) {
30  animation_.SetTweenType(ui::Tween::EASE_IN_OUT);
31  animation_.SetSlideDuration(300);
32
33  Panel* panel = new Panel;
34  panel->header = new PanelScrollerHeader(this);
35  panel->header->set_title(ASCIIToUTF16("Email"));
36  panel->container = new PanelScrollerContainer(this, new views::View());
37  panels_.push_back(panel);
38
39  panel = new Panel;
40  panel->header = new PanelScrollerHeader(this);
41  panel->header->set_title(ASCIIToUTF16("Chat"));
42  panel->container = new PanelScrollerContainer(this, new views::View());
43  panels_.push_back(panel);
44
45  panel = new Panel;
46  panel->header = new PanelScrollerHeader(this);
47  panel->header->set_title(ASCIIToUTF16("Calendar"));
48  panel->container = new PanelScrollerContainer(this, new views::View());
49  panels_.push_back(panel);
50
51  panel = new Panel;
52  panel->header = new PanelScrollerHeader(this);
53  panel->header->set_title(ASCIIToUTF16("Recent searches"));
54  panel->container = new PanelScrollerContainer(this, new views::View());
55  panels_.push_back(panel);
56
57  panel = new Panel;
58  panel->header = new PanelScrollerHeader(this);
59  panel->header->set_title(ASCIIToUTF16("Pony news"));
60  panel->container = new PanelScrollerContainer(this, new views::View());
61  panels_.push_back(panel);
62
63  // Add the containers first since they're on the bottom.
64  AddChildView(panels_[0]->container);
65  AddChildView(panels_[1]->container);
66  AddChildView(panels_[2]->container);
67  AddChildView(panels_[3]->container);
68  AddChildView(panels_[4]->container);
69
70  AddChildView(panels_[0]->header);
71  AddChildView(panels_[1]->header);
72  AddChildView(panels_[2]->header);
73  AddChildView(panels_[3]->header);
74  AddChildView(panels_[4]->header);
75}
76
77PanelScroller::~PanelScroller() {
78  STLDeleteContainerPointers(panels_.begin(), panels_.end());
79}
80
81// static
82PanelScroller* PanelScroller::CreateWindow() {
83  views::Widget* widget = views::Widget::CreateWidget(
84      views::Widget::CreateParams(views::Widget::CreateParams::TYPE_WINDOW));
85  widget->Init(NULL, gfx::Rect(0, 0, 100, 800));
86
87  PanelScroller* scroller = new PanelScroller();
88  widget->SetContentsView(scroller);
89
90  widget->Show();
91
92  return scroller;
93}
94
95void PanelScroller::ViewHierarchyChanged(bool is_add,
96                                         views::View* parent,
97                                         views::View* child) {
98  // Our child views changed without us knowing it. Stop the animation and mark
99  // us as dirty (needs_layout_ = true).
100  animation_.Stop();
101  needs_layout_ = true;
102}
103
104gfx::Size PanelScroller::GetPreferredSize() {
105  return gfx::Size(75, 200);
106}
107
108void PanelScroller::Layout() {
109/* TODO(brettw) this doesn't work for some reason.
110  if (!needs_layout_ || !animation_.IsShowing())
111    return;
112  needs_layout_ = false;*/
113
114  // The current location in the content that we're laying out. This is before
115  // scrolling is accounted for.
116  int cur_content_pos = 0;
117
118  // Number of pixels used by headers stuck to the top of the scroll area.
119  int top_header_pixel_count = 0;
120
121  int panel_count = static_cast<int>(panels_.size());
122  for (int i = 0; i < panel_count; i++) {
123    if (cur_content_pos < scroll_pos_ + top_header_pixel_count) {
124      // This panel is at least partially off the top. Put the header below the
125      // others already there.
126      panels_[i]->header->SetBoundsRect(gfx::Rect(0, top_header_pixel_count,
127                                                  width(), divider_height_));
128      top_header_pixel_count += divider_height_;
129
130    } else if (cur_content_pos > height() + scroll_pos_ -
131               (panel_count - i) * divider_height_) {
132      // When we've hit the bottom of the visible content, all the remaining
133      // headers will stack up at the bottom. Counting this header, there are
134      // (size() - i) left, which is used in the expression above.
135      int top = height() - (panel_count - i) * divider_height_;
136      panels_[i]->header->SetBoundsRect(gfx::Rect(0, top,
137                                                  width(), divider_height_));
138    } else {
139      // Normal header positioning in-flow.
140      panels_[i]->header->SetBoundsRect(
141          gfx::Rect(0, cur_content_pos - scroll_pos_, width(),
142                    divider_height_));
143    }
144
145    cur_content_pos += divider_height_;
146
147    // Now position the content. It always goes in-flow ignoring any stacked
148    // up headers at the top or bottom.
149    int container_height = panels_[i]->container->GetPreferredSize().height();
150    panels_[i]->container->SetBoundsRect(
151        gfx::Rect(0, cur_content_pos - scroll_pos_,
152                  width(), container_height));
153    cur_content_pos += container_height;
154  }
155}
156
157bool PanelScroller::OnMousePressed(const views::MouseEvent& event) {
158  return true;
159}
160
161bool PanelScroller::OnMouseDragged(const views::MouseEvent& event) {
162  return true;
163}
164
165void PanelScroller::HeaderClicked(PanelScrollerHeader* source) {
166  for (size_t i = 0; i < panels_.size(); i++) {
167    if (panels_[i]->header == source) {
168      ScrollToPanel(static_cast<int>(i));
169      return;
170    }
171  }
172  NOTREACHED() << "Invalid panel passed to HeaderClicked.";
173}
174
175void PanelScroller::ScrollToPanel(int index) {
176  int affected_panel_height =
177      panels_[index]->container->GetPreferredSize().height();
178
179  // The pixel size we need to reserve for the stuck headers.
180  int top_stuck_header_pixel_size = index * divider_height_;
181  int bottom_stuck_header_pixel_size =
182      (static_cast<int>(panels_.size()) - index - 1) * divider_height_;
183
184  // Compute the offset of the top of the panel to scroll to.
185  int space_above = 0;
186  for (int i = 0; i < index; i++) {
187    space_above += divider_height_ +
188        panels_[i]->container->GetPreferredSize().height();
189  }
190
191  // Compute the space below the top of the panel.
192  int space_below = 0;
193  for (int i = index; i < static_cast<int>(panels_.size()); i++) {
194    space_below += divider_height_ +
195        panels_[i]->container->GetPreferredSize().height();
196  }
197
198  // The scroll position of the top of the stuck headers is the space above
199  // minus the size of the headers stuck there.
200  int top_stuck_headers_scroll_pos = space_above - top_stuck_header_pixel_size;
201
202  // If the panel is already fully visible, do nothing.
203  if (scroll_pos_ <= top_stuck_headers_scroll_pos &&
204      space_above + divider_height_ + affected_panel_height -
205      bottom_stuck_header_pixel_size <= scroll_pos_ + height())
206    return;
207
208  // Compute the scroll position.
209  if (height() > space_below) {
210    // There's enough room for this panel and everything below it to fit on the
211    // screen.
212    animated_scroll_end_ = (space_above + space_below) - height();
213    if (animated_scroll_end_ > top_stuck_headers_scroll_pos)
214      animated_scroll_end_ = top_stuck_headers_scroll_pos;
215  } else if (space_above > scroll_pos_) {
216    // If we're going to be scrolling the content up, scroll just until the
217    // panel in question is fully visible.
218    animated_scroll_end_ = space_above +
219        divider_height_ + affected_panel_height +  // Size of this panel.
220        bottom_stuck_header_pixel_size -  // Leave room for these.
221        height();  // Available size in the window.
222    if (animated_scroll_end_ > top_stuck_headers_scroll_pos)
223      animated_scroll_end_ = top_stuck_headers_scroll_pos;
224  } else {
225    animated_scroll_end_ = top_stuck_headers_scroll_pos;
226  }
227
228  animated_scroll_begin_ = scroll_pos_;
229  if (animated_scroll_begin_ == animated_scroll_end_)
230    return;  // Nothing to animate.
231
232  // Start animating to the destination.
233  animation_.Reset();
234  animation_.Show();
235}
236
237void PanelScroller::AnimationProgressed(const ui::Animation* animation) {
238  scroll_pos_ = static_cast<int>(
239      static_cast<double>(animated_scroll_end_ - animated_scroll_begin_) *
240      animation_.GetCurrentValue()) + animated_scroll_begin_;
241
242  Layout();
243  SchedulePaint();
244}
245