panel_scroller.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.  Use of this
2// source code is governed by a BSD-style license that can be found in the
3// 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 "gfx/canvas.h"
15#include "views/widget/widget_gtk.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(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::WidgetGtk* widget =
84      new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW);
85  widget->set_delete_on_destroy(true);
86  widget->Init(NULL, gfx::Rect(0, 0, 100, 800));
87
88  PanelScroller* scroller = new PanelScroller();
89  widget->SetContentsView(scroller);
90
91  widget->Show();
92
93  return scroller;
94}
95
96void PanelScroller::ViewHierarchyChanged(bool is_add,
97                                         views::View* parent,
98                                         views::View* child) {
99  // Our child views changed without us knowing it. Stop the animation and mark
100  // us as dirty (needs_layout_ = true).
101  animation_.Stop();
102  needs_layout_ = true;
103}
104
105gfx::Size PanelScroller::GetPreferredSize() {
106  return gfx::Size(75, 200);
107}
108
109void PanelScroller::Layout() {
110/* TODO(brettw) this doesn't work for some reason.
111  if (!needs_layout_ || !animation_.IsShowing())
112    return;
113  needs_layout_ = false;*/
114
115  // The current location in the content that we're laying out. This is before
116  // scrolling is accounted for.
117  int cur_content_pos = 0;
118
119  // Number of pixels used by headers stuck to the top of the scroll area.
120  int top_header_pixel_count = 0;
121
122  int panel_count = static_cast<int>(panels_.size());
123  for (int i = 0; i < panel_count; i++) {
124    if (cur_content_pos < scroll_pos_ + top_header_pixel_count) {
125      // This panel is at least partially off the top. Put the header below the
126      // others already there.
127      panels_[i]->header->SetBounds(gfx::Rect(0, top_header_pixel_count,
128                                              width(), divider_height_));
129      top_header_pixel_count += divider_height_;
130
131    } else if (cur_content_pos > height() + scroll_pos_ -
132               (panel_count - i) * divider_height_) {
133      // When we've hit the bottom of the visible content, all the remaining
134      // headers will stack up at the bottom. Counting this header, there are
135      // (size() - i) left, which is used in the expression above.
136      int top = height() - (panel_count - i) * divider_height_;
137      panels_[i]->header->SetBounds(gfx::Rect(0, top,
138                                              width(), divider_height_));
139    } else {
140      // Normal header positioning in-flow.
141      panels_[i]->header->SetBounds(gfx::Rect(0, cur_content_pos - scroll_pos_,
142                                              width(), 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->SetBounds(
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::OnMouseReleased(const views::MouseEvent& event,
166                                    bool canceled) {
167}
168
169void PanelScroller::HeaderClicked(PanelScrollerHeader* source) {
170  for (size_t i = 0; i < panels_.size(); i++) {
171    if (panels_[i]->header == source) {
172      ScrollToPanel(static_cast<int>(i));
173      return;
174    }
175  }
176  NOTREACHED() << "Invalid panel passed to HeaderClicked.";
177}
178
179void PanelScroller::ScrollToPanel(int index) {
180  int affected_panel_height =
181      panels_[index]->container->GetPreferredSize().height();
182
183  // The pixel size we need to reserve for the stuck headers.
184  int top_stuck_header_pixel_size = index * divider_height_;
185  int bottom_stuck_header_pixel_size =
186      (static_cast<int>(panels_.size()) - index - 1) * divider_height_;
187
188  // Compute the offset of the top of the panel to scroll to.
189  int space_above = 0;
190  for (int i = 0; i < index; i++) {
191    space_above += divider_height_ +
192        panels_[i]->container->GetPreferredSize().height();
193  }
194
195  // Compute the space below the top of the panel.
196  int space_below = 0;
197  for (int i = index; i < static_cast<int>(panels_.size()); i++) {
198    space_below += divider_height_ +
199        panels_[i]->container->GetPreferredSize().height();
200  }
201
202  // The scroll position of the top of the stuck headers is the space above
203  // minus the size of the headers stuck there.
204  int top_stuck_headers_scroll_pos = space_above - top_stuck_header_pixel_size;
205
206  // If the panel is already fully visible, do nothing.
207  if (scroll_pos_ <= top_stuck_headers_scroll_pos &&
208      space_above + divider_height_ + affected_panel_height -
209      bottom_stuck_header_pixel_size <= scroll_pos_ + height())
210    return;
211
212  // Compute the scroll position.
213  if (height() > space_below) {
214    // There's enough room for this panel and everything below it to fit on the
215    // screen.
216    animated_scroll_end_ = (space_above + space_below) - height();
217    if (animated_scroll_end_ > top_stuck_headers_scroll_pos)
218      animated_scroll_end_ = top_stuck_headers_scroll_pos;
219  } else if (space_above > scroll_pos_) {
220    // If we're going to be scrolling the content up, scroll just until the
221    // panel in question is fully visible.
222    animated_scroll_end_ = space_above +
223        divider_height_ + affected_panel_height +  // Size of this panel.
224        bottom_stuck_header_pixel_size -  // Leave room for these.
225        height();  // Available size in the window.
226    if (animated_scroll_end_ > top_stuck_headers_scroll_pos)
227      animated_scroll_end_ = top_stuck_headers_scroll_pos;
228  } else {
229    animated_scroll_end_ = top_stuck_headers_scroll_pos;
230  }
231
232  animated_scroll_begin_ = scroll_pos_;
233  if (animated_scroll_begin_ == animated_scroll_end_)
234    return;  // Nothing to animate.
235
236  // Start animating to the destination.
237  animation_.Reset();
238  animation_.Show();
239}
240
241void PanelScroller::AnimationProgressed(const Animation* animation) {
242  scroll_pos_ = static_cast<int>(
243      static_cast<double>(animated_scroll_end_ - animated_scroll_begin_) *
244      animation_.GetCurrentValue()) + animated_scroll_begin_;
245
246  Layout();
247  SchedulePaint();
248}
249