diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-10 01:24:08 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-10 01:24:08 +0000 |
commit | cc2efcc280d9597c5eab18444a748f02e3106e33 (patch) | |
tree | cb47932e8cab712883dcb31b79f06e057266be75 /chrome/browser/gtk/tabs | |
parent | 214002870db5c61d34565467f591113a9abfcda4 (diff) | |
download | chromium_src-cc2efcc280d9597c5eab18444a748f02e3106e33.zip chromium_src-cc2efcc280d9597c5eab18444a748f02e3106e33.tar.gz chromium_src-cc2efcc280d9597c5eab18444a748f02e3106e33.tar.bz2 |
Add the New Tab button for Linux tabstrip.
Review URL: http://codereview.chromium.org/67025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13487 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/gtk/tabs')
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.cc | 246 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.h | 9 |
2 files changed, 248 insertions, 7 deletions
diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc index 5f41458..8e810ff 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc @@ -43,6 +43,169 @@ gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) { } // namespace +/////////////////////////////////////////////////////////////////////////////// +// NewTabButton +// +// Performs hit-testing and rendering of the New Tab button. +// +// TODO(jhawkins): This is shared functionality with the tab close button. +// Refactor into a new TabButton class or implement the tabstrip using +// custom gtk widgets. +class NewTabButton { + public: + // Possible button states + enum ButtonState { + BS_NORMAL, + BS_HOT, + BS_PUSHED, + BS_COUNT + }; + + explicit NewTabButton(TabStripModelDelegate* delegate) + : state_(BS_NORMAL), + mouse_pressed_(false), + delegate_(delegate) { + } + + virtual ~NewTabButton() {} + + // Returns the bounds of the button. + int x() const { return bounds_.x(); } + int y() const { return bounds_.y(); } + int width() const { return bounds_.width(); } + int height() const { return bounds_.height(); } + + // Checks whether |point| is inside the bounds of the button. + bool IsPointInBounds(const gfx::Point& point) { + GdkRegion* region = MakeRegionForButton(); + bool in_bounds = + (gdk_region_point_in(region, point.x(), point.y()) == TRUE); + gdk_region_destroy(region); + return in_bounds; + } + + // Sent by the tabstrip when the mouse moves within this button. Mouse is at + // |point|. Returns true if the tabstrip needs to be redrawn as a result + // of the motion. + bool OnMotionNotify(const gfx::Point& point) { + ButtonState state; + if (IsPointInBounds(point)) { + if (mouse_pressed_) { + state = BS_PUSHED; + } else { + state = BS_HOT; + } + } else { + state = BS_NORMAL; + } + + bool need_redraw = (state_ != state); + state_ = state; + return need_redraw; + } + + // Sent by the tabstrip when the mouse clicks within this button. Returns + // true if the tabstrip needs to be redrawn as a result of the click. + bool OnMousePress() { + if (state_ == BS_HOT) { + mouse_pressed_ = true; + state_ = BS_PUSHED; + return true; + } + + return false; + } + + // Sent by the tabstrip when the mouse click is released. + void OnMouseRelease() { + mouse_pressed_ = false; + + if (state_ == BS_PUSHED) { + delegate_->AddBlankTab(true); + state_ = BS_NORMAL; + + // Jiggle the mouse so we re-highlight the New Tab button. + HighlightNewTabButton(); + } + } + + // Paints the New Tab button into |canvas|. + void Paint(ChromeCanvasPaint* canvas) { + canvas->DrawBitmapInt(images_[state_], bounds_.x(), bounds_.y()); + } + + // Sets the image the button should use for the provided state. + void SetImage(ButtonState state, SkBitmap* bitmap) { + images_[state] = bitmap ? *bitmap : SkBitmap(); + } + + const gfx::Rect& bounds() const { return bounds_; } + + // Sets the bounds of the button. + void set_bounds(const gfx::Rect& bounds) { bounds_ = bounds; } + + private: + // Creates a clickable region of the button's visual representation. Used for + // hit-testing. Caller is responsible for destroying the region. + GdkRegion* MakeRegionForButton() const { + int w = width(); + int h = height(); + static const int kNumRegionPoints = 8; + + // These values are defined by the shape of the new tab bitmap. Should that + // bitmap ever change, these values will need to be updated. They're so + // custom it's not really worth defining constants for. + GdkPoint polygon[kNumRegionPoints] = { + { 0, 1 }, + { w - 7, 1 }, + { w - 4, 4 }, + { w, 16 }, + { w - 1, 17 }, + { 7, 17 }, + { 4, 13 }, + { 0, 1 }, + }; + + GdkRegion* region = gdk_region_polygon(polygon, kNumRegionPoints, + GDK_WINDING_RULE); + gdk_region_offset(region, x(), y()); + return region; + } + + // When the insert tab animation completes, we send the widget a message to + // simulate a mouse moved event at the current mouse position. This tickles + // the New Tab button to show the "hot" state. + void HighlightNewTabButton() { + /* get default display and screen */ + GdkDisplay* display = gdk_display_get_default(); + GdkScreen* screen = gdk_display_get_default_screen(display); + + /* get cursor position */ + int x, y; + gdk_display_get_pointer(display, NULL, &x, &y, NULL); + + /* reset cusor position */ + gdk_display_warp_pointer(display, screen, x, y); + } + + // The images used to render the different states of this button. + SkBitmap images_[BS_COUNT]; + + // The current state of the button. + ButtonState state_; + + // THe current bounds of the button. + gfx::Rect bounds_; + + // Set if the mouse is pressed anywhere inside the button. + bool mouse_pressed_; + + // Our tabstrip model delegate for opening a new tab. + TabStripModelDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(NewTabButton); +}; + //////////////////////////////////////////////////////////////////////////////// // // TabAnimation @@ -384,10 +547,11 @@ TabStripGtk::~TabStripGtk() { } void TabStripGtk::Init() { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + model_->AddObserver(this); if (!background) { - ResourceBundle &rb = ResourceBundle::GetSharedInstance(); background = rb.GetBitmapNamed(IDR_WINDOW_TOP_CENTER); } @@ -413,6 +577,16 @@ void TabStripGtk::Init() { gtk_widget_show_all(tabstrip_.get()); bounds_ = GetInitialWidgetBounds(tabstrip_.get()); + + SkBitmap* bitmap = rb.GetBitmapNamed(IDR_NEWTAB_BUTTON); + newtab_button_.reset(new NewTabButton(model_->delegate())); + newtab_button_.get()->SetImage(NewTabButton::BS_NORMAL, bitmap); + newtab_button_.get()->SetImage(NewTabButton::BS_HOT, + rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_H)); + newtab_button_.get()->SetImage(NewTabButton::BS_PUSHED, + rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_P)); + newtab_button_.get()->set_bounds( + gfx::Rect(0, 0, bitmap->width(), bitmap->height())); } void TabStripGtk::AddTabStripToBox(GtkWidget* box) { @@ -428,11 +602,14 @@ void TabStripGtk::Layout() { GenerateIdealBounds(); int tab_count = GetTabCount(); + int tab_right = 0; for (int i = 0; i < tab_count; ++i) { const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; GetTabAt(i)->SetBounds(bounds); + tab_right = bounds.right() + kTabHOffset; } + LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_); gtk_widget_queue_draw(tabstrip_.get()); } @@ -700,6 +877,26 @@ void TabStripGtk::GenerateIdealBounds() { } } +void TabStripGtk::LayoutNewTabButton(double last_tab_right, + double unselected_width) { + int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width()); + if (delta > 1 && !resize_layout_scheduled_) { + // We're shrinking tabs, so we need to anchor the New Tab button to the + // right edge of the TabStrip's bounds, rather than the right edge of the + // right-most Tab, otherwise it'll bounce when animating. + newtab_button_.get()->set_bounds( + gfx::Rect(bounds_.width() - newtab_button_.get()->bounds().width(), + kNewTabButtonVOffset, + newtab_button_.get()->bounds().width(), + newtab_button_.get()->bounds().height())); + } else { + newtab_button_.get()->set_bounds( + gfx::Rect(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, + kNewTabButtonVOffset, newtab_button_.get()->bounds().width(), + newtab_button_.get()->bounds().height())); + } +} + void TabStripGtk::GetDesiredTabWidths(int tab_count, double* unselected_width, double* selected_width) const { @@ -717,7 +914,22 @@ void TabStripGtk::GetDesiredTabWidths(int tab_count, // Determine how much space we can actually allocate to tabs. int available_width = tabstrip_.get()->allocation.width; - // TODO(jhawkins): Implement new tab button. + if (available_width_for_tabs_ < 0) { + available_width = bounds_.width(); + available_width -= + (kNewTabButtonHOffset + newtab_button_.get()->bounds().width()); + } else { + // Interesting corner case: if |available_width_for_tabs_| > the result + // of the calculation in the conditional arm above, the strip is in + // overflow. We can either use the specified width or the true available + // width here; the first preserves the consistent "leave the last tab under + // the user's mouse so they can close many tabs" behavior at the cost of + // prolonging the glitchy appearance of the overflow state, while the second + // gets us out of overflow as soon as possible but forces the user to move + // their mouse for a few tabs' worth of closing. We choose visual + // imperfection over behavioral imperfection and select the first option. + available_width = available_width_for_tabs_; + } // Calculate the desired tab widths by dividing the available space into equal // portions. Don't let tabs get larger than the "standard width" or smaller @@ -784,7 +996,7 @@ void TabStripGtk::AnimationLayout(double unselected_width) { tab->SetBounds(bounds); tab_x = end_of_tab + kTabHOffset; } - // TODO(jhawkins): Layout new tab button. + LayoutNewTabButton(tab_x, unselected_width); gtk_widget_queue_draw(tabstrip_.get()); } @@ -866,6 +1078,9 @@ gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event, if (selected_tab) selected_tab->Paint(&canvas); + // Paint the New Tab button. + tabstrip->newtab_button_.get()->Paint(&canvas); + return TRUE; } @@ -905,6 +1120,9 @@ gboolean TabStripGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, gtk_widget_queue_draw(tabstrip->tabstrip_.get()); } + if (tabstrip->newtab_button_.get()->OnMotionNotify(point)) + gtk_widget_queue_draw(tabstrip->tabstrip_.get()); + return TRUE; } @@ -954,13 +1172,23 @@ gboolean TabStripGtk::OnMousePress(GtkWidget* widget, GdkEventButton* event, TabStripGtk* tabstrip) { // TODO(jhawkins): Handle middle and right-click. // TODO(jhawkins): Are there no gdk constants for event->button? - if (tabstrip->hover_index_ == -1 || event->button != 1) + if (event->button != 1) return TRUE; - if (tabstrip->GetTabAt(tabstrip->hover_index_)->OnMousePress()) + gfx::Point point(event->x, event->y); + if (tabstrip->hover_index_ == -1) { + if (tabstrip->newtab_button_.get()->IsPointInBounds(point) && + tabstrip->newtab_button_.get()->OnMousePress()) + gtk_widget_queue_draw(tabstrip->tabstrip_.get()); + + return TRUE; + } + + if (tabstrip->GetTabAt(tabstrip->hover_index_)->OnMousePress()) { gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - else if (tabstrip->hover_index_ != tabstrip->model()->selected_index()) + } else if (tabstrip->hover_index_ != tabstrip->model()->selected_index()) { tabstrip->model()->SelectTabContentsAt(tabstrip->hover_index_, true); + } return TRUE; } @@ -971,8 +1199,12 @@ gboolean TabStripGtk::OnMouseRelease(GtkWidget* widget, GdkEventButton* event, if (event->button != 1) return TRUE; - if (tabstrip->hover_index_ != -1) + gfx::Point point(event->x, event->y); + if (tabstrip->hover_index_ != -1) { tabstrip->GetTabAt(tabstrip->hover_index_)->OnMouseRelease(); + } else if (tabstrip->newtab_button_.get()->IsPointInBounds(point)) { + tabstrip->newtab_button_.get()->OnMouseRelease(); + } return TRUE; } diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h index d80312c..c0f2c17 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h @@ -15,6 +15,8 @@ #include "chrome/common/owned_widget_gtk.h" #include "skia/include/SkBitmap.h" +class NewTabButton; + class TabStripGtk : public TabStripModelObserver, public TabGtk::TabDelegate { public: @@ -145,6 +147,10 @@ class TabStripGtk : public TabStripModelObserver, // stable representations of Tab positions. void GenerateIdealBounds(); + // Lays out the New Tab button, assuming the right edge of the last Tab on + // the TabStrip at |last_tab_right|. + void LayoutNewTabButton(double last_tab_right, double unselected_width); + // -- Animations ------------------------------------------------------------- // A generic Layout method for various classes of TabStrip animations, @@ -205,6 +211,9 @@ class TabStripGtk : public TabStripModelObserver, // The currently running animation. scoped_ptr<TabAnimation> active_animation_; + // The New Tab button. + scoped_ptr<NewTabButton> newtab_button_; + DISALLOW_COPY_AND_ASSIGN(TabStripGtk); }; |