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/ui/touch/tabs/touch_tab_strip.h" 6 7 #include <algorithm> 8 #include <cmath> 9 10 #include "chrome/browser/ui/touch/tabs/touch_tab.h" 11 #include "chrome/browser/ui/view_ids.h" 12 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" 13 #include "ui/gfx/canvas_skia.h" 14 #include "views/metrics.h" 15 #include "views/window/non_client_view.h" 16 #include "views/window/window.h" 17 18 static const int kTouchTabStripHeight = 64; 19 static const int kTouchTabWidth = 64; 20 static const int kTouchTabHeight = 64; 21 static const int kScrollThreshold = 4; 22 23 TouchTabStrip::TouchTabStrip(TabStripController* controller) 24 : BaseTabStrip(controller, BaseTabStrip::HORIZONTAL_TAB_STRIP), 25 in_tab_close_(false), 26 last_tap_time_(base::Time::FromInternalValue(0)), 27 last_tapped_view_(NULL), 28 initial_mouse_x_(0), 29 initial_scroll_offset_(0), 30 scroll_offset_(0), 31 scrolling_(false), 32 initial_tab_(NULL), 33 min_scroll_offset_(0) { 34 Init(); 35 } 36 37 TouchTabStrip::~TouchTabStrip() { 38 // The animations may reference the tabs. Shut down the animation before we 39 // delete the tabs. 40 StopAnimating(false); 41 42 DestroyDragController(); 43 44 // The children (tabs) may callback to us from their destructor. Delete them 45 // so that if they call back we aren't in a weird state. 46 RemoveAllChildViews(true); 47 } 48 49 //////////////////////////////////////////////////////////////////////////////// 50 // TouchTabStrip, AbstractTabStripView implementation: 51 52 bool TouchTabStrip::IsPositionInWindowCaption(const gfx::Point& point) { 53 // The entire tabstrip is mine. No part of it belongs to the window caption. 54 return false; 55 } 56 57 void TouchTabStrip::SetBackgroundOffset(const gfx::Point& offset) { 58 for (int i = 0; i < tab_count(); ++i) 59 GetTabAtTabDataIndex(i)->set_background_offset(offset); 60 } 61 62 //////////////////////////////////////////////////////////////////////////////// 63 // TouchTabStrip, BaseTabStrip implementation: 64 65 void TouchTabStrip::PrepareForCloseAt(int model_index) { 66 if (!in_tab_close_ && IsAnimating()) { 67 // Cancel any current animations. We do this as remove uses the current 68 // ideal bounds and we need to know ideal bounds is in a good state. 69 StopAnimating(true); 70 } 71 72 in_tab_close_ = true; 73 } 74 75 void TouchTabStrip::StartHighlight(int model_index) { 76 } 77 78 void TouchTabStrip::StopAllHighlighting() { 79 } 80 81 BaseTab* TouchTabStrip::CreateTabForDragging() { 82 return NULL; 83 } 84 85 void TouchTabStrip::RemoveTabAt(int model_index) { 86 StartRemoveTabAnimation(model_index); 87 } 88 89 void TouchTabStrip::SelectTabAt(int old_model_index, int new_model_index) { 90 SchedulePaint(); 91 } 92 93 void TouchTabStrip::TabTitleChangedNotLoading(int model_index) { 94 } 95 96 BaseTab* TouchTabStrip::CreateTab() { 97 return new TouchTab(this); 98 } 99 100 void TouchTabStrip::StartInsertTabAnimation(int model_index) { 101 PrepareForAnimation(); 102 103 in_tab_close_ = false; 104 105 GenerateIdealBounds(); 106 107 int tab_data_index = ModelIndexToTabIndex(model_index); 108 BaseTab* tab = base_tab_at_tab_index(tab_data_index); 109 if (model_index == 0) { 110 tab->SetBounds(0, ideal_bounds(tab_data_index).y(), 0, 111 ideal_bounds(tab_data_index).height()); 112 } else { 113 BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1); 114 tab->SetBounds(last_tab->bounds().right(), 115 ideal_bounds(tab_data_index).y(), 0, 116 ideal_bounds(tab_data_index).height()); 117 } 118 119 AnimateToIdealBounds(); 120 } 121 122 void TouchTabStrip::AnimateToIdealBounds() { 123 for (int i = 0; i < tab_count(); ++i) { 124 TouchTab* tab = GetTabAtTabDataIndex(i); 125 if (!tab->closing() && !tab->dragging()) 126 bounds_animator().AnimateViewTo(tab, ideal_bounds(i)); 127 } 128 } 129 130 bool TouchTabStrip::ShouldHighlightCloseButtonAfterRemove() { 131 return in_tab_close_; 132 } 133 134 void TouchTabStrip::GenerateIdealBounds() { 135 gfx::Rect bounds; 136 int tab_x = 0; 137 int tab_y = 0; 138 for (int i = 0; i < tab_count(); ++i) { 139 TouchTab* tab = GetTabAtTabDataIndex(i); 140 if (!tab->closing()) { 141 int x = tab_x + scroll_offset_; 142 if (tab->IsSelected()) { 143 // limit the extent to which this tab can be displaced. 144 x = std::min(std::max(0, x), width() - kTouchTabWidth); 145 } 146 set_ideal_bounds(i, gfx::Rect(x, tab_y, 147 kTouchTabWidth, kTouchTabHeight)); 148 // offset the next tab to the right by the width of this tab 149 tab_x += kTouchTabWidth; 150 } 151 } 152 min_scroll_offset_ = std::min(0, width() - tab_x); 153 } 154 155 void TouchTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs, 156 BaseTab* active_tab, 157 const gfx::Point& location, 158 bool initial_drag) { 159 // Not needed as dragging isn't supported. 160 NOTIMPLEMENTED(); 161 } 162 163 void TouchTabStrip::CalculateBoundsForDraggedTabs( 164 const std::vector<BaseTab*>& tabs, 165 std::vector<gfx::Rect>* bounds) { 166 // Not needed as dragging isn't supported. 167 NOTIMPLEMENTED(); 168 } 169 170 int TouchTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) { 171 // Not needed as dragging isn't supported. 172 NOTIMPLEMENTED(); 173 return 0; 174 } 175 176 // TODO(wyck): Someday we might like to get a "scroll" interaction event by way 177 // of views, triggered by the gesture manager, and/or mouse scroll wheel. 178 // For now, we're just handling a single scroll with these mouse events: 179 // OnMousePressed, OnMouseDragged, and OnMouseReleased. 180 181 bool TouchTabStrip::OnMousePressed(const views::MouseEvent& event) { 182 // When we press the mouse button, we begin a drag 183 BeginScroll(event.location()); 184 return true; 185 } 186 187 bool TouchTabStrip::OnMouseDragged(const views::MouseEvent& event) { 188 ContinueScroll(event.location()); 189 return true; 190 } 191 192 void TouchTabStrip::OnMouseReleased(const views::MouseEvent& event) { 193 EndScroll(event.location()); 194 } 195 196 void TouchTabStrip::OnMouseCaptureLost() { 197 CancelScroll(); 198 } 199 200 void TouchTabStrip::BeginScroll(const gfx::Point& point ) { 201 initial_mouse_x_ = point.x(); 202 initial_scroll_offset_ = scroll_offset_; 203 initial_tab_ = static_cast<TouchTab*>(GetTabAtLocal(point)); 204 } 205 206 void TouchTabStrip::ContinueScroll(const gfx::Point& point) { 207 int delta_x = point.x() - initial_mouse_x_; 208 if (std::abs(delta_x) > kScrollThreshold) 209 scrolling_ = true; 210 if (scrolling_) 211 ScrollTo(delta_x); 212 DoLayout(); 213 SchedulePaint(); 214 } 215 216 void TouchTabStrip::EndScroll(const gfx::Point& point) { 217 int delta_x = point.x() - initial_mouse_x_; 218 if (scrolling_) { 219 scrolling_ = false; 220 ScrollTo(delta_x); 221 StopAnimating(false); 222 GenerateIdealBounds(); 223 AnimateToIdealBounds(); 224 } else { 225 TouchTab* tab = static_cast<TouchTab*>(GetTabAtLocal(point)); 226 if (tab && tab == initial_tab_) 227 SelectTab(tab); 228 DoLayout(); 229 SchedulePaint(); 230 } 231 initial_tab_ = NULL; 232 } 233 234 void TouchTabStrip::CancelScroll() { 235 // Cancel the scroll by scrolling back to the initial position (deltax = 0). 236 ScrollTo(0); 237 StopAnimating(false); 238 GenerateIdealBounds(); 239 AnimateToIdealBounds(); 240 } 241 242 void TouchTabStrip::ScrollTo(int delta_x) { 243 scroll_offset_ = initial_scroll_offset_ + delta_x; 244 // Limit the scrolling here. 245 // When scrolling beyond the limits of min and max offsets, the displacement 246 // is adjusted to 25% of what would normally applied (divided by 4). 247 // Perhaps in the future, Hooke's law could be used to model more physically 248 // based spring-like behavior. 249 int max_scroll_offset = 0; // Because there's never content to the left of 0. 250 if (scroll_offset_ > max_scroll_offset) { 251 if (scrolling_) { 252 scroll_offset_ = max_scroll_offset 253 + std::min((scroll_offset_ - max_scroll_offset) / 4, 254 kTouchTabWidth); 255 } else { 256 scroll_offset_ = max_scroll_offset; 257 } 258 } 259 if (scroll_offset_ < min_scroll_offset_) { 260 if (scrolling_) { 261 scroll_offset_ = min_scroll_offset_ 262 + std::max((scroll_offset_ - min_scroll_offset_) / 4, 263 -kTouchTabWidth); 264 } else { 265 scroll_offset_ = min_scroll_offset_; 266 } 267 } 268 } 269 270 TouchTab* TouchTabStrip::GetTabAtTabDataIndex(int tab_data_index) const { 271 return static_cast<TouchTab*>(base_tab_at_tab_index(tab_data_index)); 272 } 273 274 //////////////////////////////////////////////////////////////////////////////// 275 // TouchTabStrip, private: 276 277 void TouchTabStrip::Init() { 278 SetID(VIEW_ID_TAB_STRIP); 279 } 280 281 //////////////////////////////////////////////////////////////////////////////// 282 // TouchTabStrip, views::View overrides, private: 283 284 gfx::Size TouchTabStrip::GetPreferredSize() { 285 return gfx::Size(0, kTouchTabStripHeight); 286 } 287 288 void TouchTabStrip::PaintChildren(gfx::Canvas* canvas) { 289 // Tabs are painted in reverse order, so they stack to the left. 290 TouchTab* selected_tab = NULL; 291 TouchTab* dragging_tab = NULL; 292 293 for (int i = tab_count() - 1; i >= 0; --i) { 294 TouchTab* tab = GetTabAtTabDataIndex(i); 295 // We must ask the _Tab's_ model, not ourselves, because in some situations 296 // the model will be different to this object, e.g. when a Tab is being 297 // removed after its TabContents has been destroyed. 298 if (tab->dragging()) { 299 dragging_tab = tab; 300 } else if (!tab->IsSelected()) { 301 tab->Paint(canvas); 302 } else { 303 selected_tab = tab; 304 } 305 } 306 307 if (GetWindow()->non_client_view()->UseNativeFrame()) { 308 // Make sure unselected tabs are somewhat transparent. 309 SkPaint paint; 310 paint.setColor(SkColorSetARGB(200, 255, 255, 255)); 311 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 312 paint.setStyle(SkPaint::kFill_Style); 313 canvas->DrawRectInt(0, 0, width(), 314 height() - 2, // Visible region that overlaps the toolbar. 315 paint); 316 } 317 318 // Paint the selected tab last, so it overlaps all the others. 319 if (selected_tab) 320 selected_tab->Paint(canvas); 321 322 // And the dragged tab. 323 if (dragging_tab) 324 dragging_tab->Paint(canvas); 325 } 326 327 views::View::TouchStatus TouchTabStrip::OnTouchEvent( 328 const views::TouchEvent& event) { 329 if (event.type() != ui::ET_TOUCH_PRESSED) 330 return TOUCH_STATUS_UNKNOWN; 331 332 views::View* view = GetEventHandlerForPoint(event.location()); 333 if (view && view != this && view->GetID() != VIEW_ID_TAB) 334 return TOUCH_STATUS_UNKNOWN; 335 336 base::TimeDelta delta = event.time_stamp() - last_tap_time_; 337 338 if (delta.InMilliseconds() < views::GetDoubleClickInterval() && 339 view == last_tapped_view_) { 340 // If double tapped the empty space, open a new tab. If double tapped a tab, 341 // close it. 342 if (view == this) 343 controller()->CreateNewTab(); 344 else 345 CloseTab(static_cast<BaseTab*>(view)); 346 347 last_tap_time_ = base::Time::FromInternalValue(0); 348 last_tapped_view_ = NULL; 349 return TOUCH_STATUS_END; 350 } 351 352 last_tap_time_ = event.time_stamp(); 353 last_tapped_view_ = view; 354 return TOUCH_STATUS_UNKNOWN; 355 } 356 357 void TouchTabStrip::ViewHierarchyChanged(bool is_add, 358 View* parent, 359 View* child) { 360 if (!is_add && last_tapped_view_ == child) 361 last_tapped_view_ = NULL; 362 } 363