// 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 "views/controls/scrollbar/bitmap_scroll_bar.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/message_loop.h" #include "base/string16.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" #include "grit/ui_strings.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/keycodes/keyboard_codes.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/canvas.h" #include "views/controls/menu/menu.h" #include "views/controls/scroll_view.h" #include "views/controls/scrollbar/base_scroll_bar_thumb.h" #include "views/widget/widget.h" #if defined(OS_LINUX) #include "views/screen.h" #endif #undef min #undef max namespace views { namespace { // The distance the mouse can be dragged outside the bounds of the thumb during // dragging before the scrollbar will snap back to its regular position. const int kScrollThumbDragOutSnap = 100; /////////////////////////////////////////////////////////////////////////////// // // AutorepeatButton // // A button that activates on mouse pressed rather than released, and that // continues to fire the clicked action as the mouse button remains pressed // down on the button. // /////////////////////////////////////////////////////////////////////////////// class AutorepeatButton : public ImageButton { public: explicit AutorepeatButton(ButtonListener* listener) : ImageButton(listener), ALLOW_THIS_IN_INITIALIZER_LIST(repeater_( base::Bind(&AutorepeatButton::NotifyClick, base::Unretained(this)))) { } virtual ~AutorepeatButton() {} protected: virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE { Button::NotifyClick(event); repeater_.Start(); return true; } virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE { OnMouseCaptureLost(); } virtual void OnMouseCaptureLost() OVERRIDE { repeater_.Stop(); } private: void NotifyClick() { #if defined(OS_WIN) DWORD pos = GetMessagePos(); POINTS points = MAKEPOINTS(pos); gfx::Point cursor_point(points.x, points.y); #elif defined(OS_LINUX) gfx::Point cursor_point = Screen::GetCursorScreenPoint(); #endif views::MouseEvent event(ui::ET_MOUSE_RELEASED, cursor_point.x(), cursor_point.y(), ui::EF_LEFT_BUTTON_DOWN); Button::NotifyClick(event); } // The repeat controller that we use to repeatedly click the button when the // mouse button is down. RepeatController repeater_; DISALLOW_COPY_AND_ASSIGN(AutorepeatButton); }; /////////////////////////////////////////////////////////////////////////////// // // BitmapScrollBarThumb // // A view that acts as the thumb in the scroll bar track that the user can // drag to scroll the associated contents view within the viewport. // /////////////////////////////////////////////////////////////////////////////// class BitmapScrollBarThumb : public BaseScrollBarThumb { public: explicit BitmapScrollBarThumb(BitmapScrollBar* scroll_bar) : BaseScrollBarThumb(scroll_bar), scroll_bar_(scroll_bar) { } virtual ~BitmapScrollBarThumb() { } // View overrides: virtual gfx::Size GetPreferredSize() OVERRIDE { return gfx::Size(background_bitmap()->width(), start_cap_bitmap()->height() + end_cap_bitmap()->height() + grippy_bitmap()->height()); } protected: // View overrides: virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { canvas->DrawBitmapInt(*start_cap_bitmap(), 0, 0); int top_cap_height = start_cap_bitmap()->height(); int bottom_cap_height = end_cap_bitmap()->height(); int thumb_body_height = height() - top_cap_height - bottom_cap_height; canvas->TileImageInt(*background_bitmap(), 0, top_cap_height, background_bitmap()->width(), thumb_body_height); canvas->DrawBitmapInt(*end_cap_bitmap(), 0, height() - bottom_cap_height); // Paint the grippy over the track. int grippy_x = (width() - grippy_bitmap()->width()) / 2; int grippy_y = (thumb_body_height - grippy_bitmap()->height()) / 2; canvas->DrawBitmapInt(*grippy_bitmap(), grippy_x, grippy_y); } private: // Returns the bitmap rendered at the start of the thumb. SkBitmap* start_cap_bitmap() const { return scroll_bar_->images_[BitmapScrollBar::THUMB_START_CAP][GetState()]; } // Returns the bitmap rendered at the end of the thumb. SkBitmap* end_cap_bitmap() const { return scroll_bar_->images_[BitmapScrollBar::THUMB_END_CAP][GetState()]; } // Returns the bitmap that is tiled in the background of the thumb between // the start and the end caps. SkBitmap* background_bitmap() const { return scroll_bar_->images_[BitmapScrollBar::THUMB_MIDDLE][GetState()]; } // Returns the bitmap that is rendered in the middle of the thumb // transparently over the background bitmap. SkBitmap* grippy_bitmap() const { return scroll_bar_->images_[BitmapScrollBar::THUMB_GRIPPY] [CustomButton::BS_NORMAL]; } // The BitmapScrollBar that owns us. BitmapScrollBar* scroll_bar_; DISALLOW_COPY_AND_ASSIGN(BitmapScrollBarThumb); }; } // namespace /////////////////////////////////////////////////////////////////////////////// // BitmapScrollBar, public: BitmapScrollBar::BitmapScrollBar(bool horizontal, bool show_scroll_buttons) : BaseScrollBar(horizontal, new BitmapScrollBarThumb(this)), ALLOW_THIS_IN_INITIALIZER_LIST(prev_button_(new AutorepeatButton(this))), ALLOW_THIS_IN_INITIALIZER_LIST(next_button_(new AutorepeatButton(this))), show_scroll_buttons_(show_scroll_buttons) { if (!show_scroll_buttons_) { prev_button_->SetVisible(false); next_button_->SetVisible(false); } AddChildView(prev_button_); AddChildView(next_button_); set_context_menu_controller(this); prev_button_->set_context_menu_controller(this); next_button_->set_context_menu_controller(this); } void BitmapScrollBar::SetImage(ScrollBarPart part, CustomButton::ButtonState state, SkBitmap* bitmap) { DCHECK(part < PART_COUNT); DCHECK(state < CustomButton::BS_COUNT); switch (part) { case PREV_BUTTON: prev_button_->SetImage(state, bitmap); break; case NEXT_BUTTON: next_button_->SetImage(state, bitmap); break; case THUMB_START_CAP: case THUMB_MIDDLE: case THUMB_END_CAP: case THUMB_GRIPPY: case THUMB_TRACK: images_[part][state] = bitmap; break; } } int BitmapScrollBar::GetLayoutSize() const { gfx::Size prefsize = prev_button_->GetPreferredSize(); return IsHorizontal() ? prefsize.height() : prefsize.width(); } gfx::Rect BitmapScrollBar::GetTrackBounds() const { gfx::Size prefsize = prev_button_->GetPreferredSize(); if (IsHorizontal()) { if (!show_scroll_buttons_) prefsize.set_width(0); int new_width = std::max(0, width() - (prefsize.width() * 2)); gfx::Rect track_bounds(prefsize.width(), 0, new_width, prefsize.height()); return track_bounds; } if (!show_scroll_buttons_) prefsize.set_height(0); gfx::Rect track_bounds(0, prefsize.height(), prefsize.width(), std::max(0, height() - (prefsize.height() * 2))); return track_bounds; } /////////////////////////////////////////////////////////////////////////////// // BitmapScrollBar, View implementation: gfx::Size BitmapScrollBar::GetPreferredSize() { // In this case, we're returning the desired width of the scrollbar and its // minimum allowable height. gfx::Size button_prefsize = prev_button_->GetPreferredSize(); return gfx::Size(button_prefsize.width(), button_prefsize.height() * 2); } void BitmapScrollBar::Layout() { // Size and place the two scroll buttons. if (show_scroll_buttons_) { gfx::Size prefsize = prev_button_->GetPreferredSize(); prev_button_->SetBounds(0, 0, prefsize.width(), prefsize.height()); prefsize = next_button_->GetPreferredSize(); if (IsHorizontal()) { next_button_->SetBounds(width() - prefsize.width(), 0, prefsize.width(), prefsize.height()); } else { next_button_->SetBounds(0, height() - prefsize.height(), prefsize.width(), prefsize.height()); } } else { prev_button_->SetBounds(0, 0, 0, 0); next_button_->SetBounds(0, 0, 0, 0); } BaseScrollBarThumb* thumb = GetThumb(); // Size and place the thumb gfx::Size thumb_prefsize = thumb->GetPreferredSize(); gfx::Rect track_bounds = GetTrackBounds(); // Preserve the height/width of the thumb (depending on orientation) as set // by the last call to |Update|, but coerce the width/height to be the // appropriate value for the bitmaps provided. if (IsHorizontal()) { thumb->SetBounds(thumb->x(), thumb->y(), thumb->width(), thumb_prefsize.height()); } else { thumb->SetBounds(thumb->x(), thumb->y(), thumb_prefsize.width(), thumb->height()); } // Hide the thumb if the track isn't tall enough to display even a tiny // thumb. The user can only use the mousewheel, scroll buttons or keyboard // in this scenario. if ((IsHorizontal() && (track_bounds.width() < thumb_prefsize.width()) || (!IsHorizontal() && (track_bounds.height() < thumb_prefsize.height())))) { thumb->SetVisible(false); } else if (!thumb->IsVisible()) { thumb->SetVisible(true); } } /////////////////////////////////////////////////////////////////////////////// // BitmapScrollBar, View implementation: void BitmapScrollBar::OnPaint(gfx::Canvas* canvas) { // Paint the track. gfx::Rect track_bounds = GetTrackBounds(); canvas->TileImageInt(*images_[THUMB_TRACK][GetThumbTrackState()], track_bounds.x(), track_bounds.y(), track_bounds.width(), track_bounds.height()); } /////////////////////////////////////////////////////////////////////////////// // BitmapScrollBar, ButtonListener implementation: void BitmapScrollBar::ButtonPressed(Button* sender, const views::Event& event) { if (sender == prev_button_) { ScrollByAmount(SCROLL_PREV_LINE); } else if (sender == next_button_) { ScrollByAmount(SCROLL_NEXT_LINE); } } } // namespace views