| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/panels/panel_scroller.h" |
| |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/stl_util-inl.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/chromeos/panels/panel_scroller_container.h" |
| #include "chrome/browser/chromeos/panels/panel_scroller_header.h" |
| #include "gfx/canvas.h" |
| #include "views/widget/widget_gtk.h" |
| |
| struct PanelScroller::Panel { |
| PanelScrollerHeader* header; |
| PanelScrollerContainer* container; |
| }; |
| |
| PanelScroller::PanelScroller() |
| : views::View(), |
| divider_height_(18), |
| needs_layout_(true), |
| scroll_pos_(0), |
| ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)), |
| animated_scroll_begin_(0), |
| animated_scroll_end_(0) { |
| animation_.SetTweenType(ui::Tween::EASE_IN_OUT); |
| animation_.SetSlideDuration(300); |
| |
| Panel* panel = new Panel; |
| panel->header = new PanelScrollerHeader(this); |
| panel->header->set_title(ASCIIToUTF16("Email")); |
| panel->container = new PanelScrollerContainer(this, new views::View()); |
| panels_.push_back(panel); |
| |
| panel = new Panel; |
| panel->header = new PanelScrollerHeader(this); |
| panel->header->set_title(ASCIIToUTF16("Chat")); |
| panel->container = new PanelScrollerContainer(this, new views::View()); |
| panels_.push_back(panel); |
| |
| panel = new Panel; |
| panel->header = new PanelScrollerHeader(this); |
| panel->header->set_title(ASCIIToUTF16("Calendar")); |
| panel->container = new PanelScrollerContainer(this, new views::View()); |
| panels_.push_back(panel); |
| |
| panel = new Panel; |
| panel->header = new PanelScrollerHeader(this); |
| panel->header->set_title(ASCIIToUTF16("Recent searches")); |
| panel->container = new PanelScrollerContainer(this, new views::View()); |
| panels_.push_back(panel); |
| |
| panel = new Panel; |
| panel->header = new PanelScrollerHeader(this); |
| panel->header->set_title(ASCIIToUTF16("Pony news")); |
| panel->container = new PanelScrollerContainer(this, new views::View()); |
| panels_.push_back(panel); |
| |
| // Add the containers first since they're on the bottom. |
| AddChildView(panels_[0]->container); |
| AddChildView(panels_[1]->container); |
| AddChildView(panels_[2]->container); |
| AddChildView(panels_[3]->container); |
| AddChildView(panels_[4]->container); |
| |
| AddChildView(panels_[0]->header); |
| AddChildView(panels_[1]->header); |
| AddChildView(panels_[2]->header); |
| AddChildView(panels_[3]->header); |
| AddChildView(panels_[4]->header); |
| } |
| |
| PanelScroller::~PanelScroller() { |
| STLDeleteContainerPointers(panels_.begin(), panels_.end()); |
| } |
| |
| // static |
| PanelScroller* PanelScroller::CreateWindow() { |
| views::WidgetGtk* widget = |
| new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW); |
| widget->set_delete_on_destroy(true); |
| widget->Init(NULL, gfx::Rect(0, 0, 100, 800)); |
| |
| PanelScroller* scroller = new PanelScroller(); |
| widget->SetContentsView(scroller); |
| |
| widget->Show(); |
| |
| return scroller; |
| } |
| |
| void PanelScroller::ViewHierarchyChanged(bool is_add, |
| views::View* parent, |
| views::View* child) { |
| // Our child views changed without us knowing it. Stop the animation and mark |
| // us as dirty (needs_layout_ = true). |
| animation_.Stop(); |
| needs_layout_ = true; |
| } |
| |
| gfx::Size PanelScroller::GetPreferredSize() { |
| return gfx::Size(75, 200); |
| } |
| |
| void PanelScroller::Layout() { |
| /* TODO(brettw) this doesn't work for some reason. |
| if (!needs_layout_ || !animation_.IsShowing()) |
| return; |
| needs_layout_ = false;*/ |
| |
| // The current location in the content that we're laying out. This is before |
| // scrolling is accounted for. |
| int cur_content_pos = 0; |
| |
| // Number of pixels used by headers stuck to the top of the scroll area. |
| int top_header_pixel_count = 0; |
| |
| int panel_count = static_cast<int>(panels_.size()); |
| for (int i = 0; i < panel_count; i++) { |
| if (cur_content_pos < scroll_pos_ + top_header_pixel_count) { |
| // This panel is at least partially off the top. Put the header below the |
| // others already there. |
| panels_[i]->header->SetBounds(gfx::Rect(0, top_header_pixel_count, |
| width(), divider_height_)); |
| top_header_pixel_count += divider_height_; |
| |
| } else if (cur_content_pos > height() + scroll_pos_ - |
| (panel_count - i) * divider_height_) { |
| // When we've hit the bottom of the visible content, all the remaining |
| // headers will stack up at the bottom. Counting this header, there are |
| // (size() - i) left, which is used in the expression above. |
| int top = height() - (panel_count - i) * divider_height_; |
| panels_[i]->header->SetBounds(gfx::Rect(0, top, |
| width(), divider_height_)); |
| } else { |
| // Normal header positioning in-flow. |
| panels_[i]->header->SetBounds(gfx::Rect(0, cur_content_pos - scroll_pos_, |
| width(), divider_height_)); |
| } |
| |
| cur_content_pos += divider_height_; |
| |
| // Now position the content. It always goes in-flow ignoring any stacked |
| // up headers at the top or bottom. |
| int container_height = panels_[i]->container->GetPreferredSize().height(); |
| panels_[i]->container->SetBounds( |
| gfx::Rect(0, cur_content_pos - scroll_pos_, |
| width(), container_height)); |
| cur_content_pos += container_height; |
| } |
| } |
| |
| bool PanelScroller::OnMousePressed(const views::MouseEvent& event) { |
| return true; |
| } |
| |
| bool PanelScroller::OnMouseDragged(const views::MouseEvent& event) { |
| return true; |
| } |
| |
| void PanelScroller::OnMouseReleased(const views::MouseEvent& event, |
| bool canceled) { |
| } |
| |
| void PanelScroller::HeaderClicked(PanelScrollerHeader* source) { |
| for (size_t i = 0; i < panels_.size(); i++) { |
| if (panels_[i]->header == source) { |
| ScrollToPanel(static_cast<int>(i)); |
| return; |
| } |
| } |
| NOTREACHED() << "Invalid panel passed to HeaderClicked."; |
| } |
| |
| void PanelScroller::ScrollToPanel(int index) { |
| int affected_panel_height = |
| panels_[index]->container->GetPreferredSize().height(); |
| |
| // The pixel size we need to reserve for the stuck headers. |
| int top_stuck_header_pixel_size = index * divider_height_; |
| int bottom_stuck_header_pixel_size = |
| (static_cast<int>(panels_.size()) - index - 1) * divider_height_; |
| |
| // Compute the offset of the top of the panel to scroll to. |
| int space_above = 0; |
| for (int i = 0; i < index; i++) { |
| space_above += divider_height_ + |
| panels_[i]->container->GetPreferredSize().height(); |
| } |
| |
| // Compute the space below the top of the panel. |
| int space_below = 0; |
| for (int i = index; i < static_cast<int>(panels_.size()); i++) { |
| space_below += divider_height_ + |
| panels_[i]->container->GetPreferredSize().height(); |
| } |
| |
| // The scroll position of the top of the stuck headers is the space above |
| // minus the size of the headers stuck there. |
| int top_stuck_headers_scroll_pos = space_above - top_stuck_header_pixel_size; |
| |
| // If the panel is already fully visible, do nothing. |
| if (scroll_pos_ <= top_stuck_headers_scroll_pos && |
| space_above + divider_height_ + affected_panel_height - |
| bottom_stuck_header_pixel_size <= scroll_pos_ + height()) |
| return; |
| |
| // Compute the scroll position. |
| if (height() > space_below) { |
| // There's enough room for this panel and everything below it to fit on the |
| // screen. |
| animated_scroll_end_ = (space_above + space_below) - height(); |
| if (animated_scroll_end_ > top_stuck_headers_scroll_pos) |
| animated_scroll_end_ = top_stuck_headers_scroll_pos; |
| } else if (space_above > scroll_pos_) { |
| // If we're going to be scrolling the content up, scroll just until the |
| // panel in question is fully visible. |
| animated_scroll_end_ = space_above + |
| divider_height_ + affected_panel_height + // Size of this panel. |
| bottom_stuck_header_pixel_size - // Leave room for these. |
| height(); // Available size in the window. |
| if (animated_scroll_end_ > top_stuck_headers_scroll_pos) |
| animated_scroll_end_ = top_stuck_headers_scroll_pos; |
| } else { |
| animated_scroll_end_ = top_stuck_headers_scroll_pos; |
| } |
| |
| animated_scroll_begin_ = scroll_pos_; |
| if (animated_scroll_begin_ == animated_scroll_end_) |
| return; // Nothing to animate. |
| |
| // Start animating to the destination. |
| animation_.Reset(); |
| animation_.Show(); |
| } |
| |
| void PanelScroller::AnimationProgressed(const ui::Animation* animation) { |
| scroll_pos_ = static_cast<int>( |
| static_cast<double>(animated_scroll_end_ - animated_scroll_begin_) * |
| animation_.GetCurrentValue()) + animated_scroll_begin_; |
| |
| Layout(); |
| SchedulePaint(); |
| } |