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 17 struct PanelScroller::Panel { 18 PanelScrollerHeader* header; 19 PanelScrollerContainer* container; 20 }; 21 22 PanelScroller::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 77 PanelScroller::~PanelScroller() { 78 STLDeleteContainerPointers(panels_.begin(), panels_.end()); 79 } 80 81 // static 82 PanelScroller* 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 95 void 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 104 gfx::Size PanelScroller::GetPreferredSize() { 105 return gfx::Size(75, 200); 106 } 107 108 void 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 157 bool PanelScroller::OnMousePressed(const views::MouseEvent& event) { 158 return true; 159 } 160 161 bool PanelScroller::OnMouseDragged(const views::MouseEvent& event) { 162 return true; 163 } 164 165 void 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 175 void 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 237 void 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