| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "ui/views/bubble/bubble_border_2.h" | |
| 6 | |
| 7 #include <algorithm> // for std::max | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "ui/gfx/canvas.h" | |
| 11 #include "ui/gfx/path.h" | |
| 12 #include "ui/gfx/rect.h" | |
| 13 #include "ui/gfx/screen.h" | |
| 14 #include "ui/gfx/skia_util.h" | |
| 15 | |
| 16 namespace { | |
| 17 | |
| 18 // Bubble border corner radius. | |
| 19 const int kCornerRadius = 2; | |
| 20 | |
| 21 // Arrow width and height. | |
| 22 const int kArrowHeight = 10; | |
| 23 const int kArrowWidth = 20; | |
| 24 | |
| 25 const int kBorderSize = 1; | |
| 26 const SkColor kBorderColor = SkColorSetARGB(0x26, 0, 0, 0); | |
| 27 const SkColor kBackgroundColor = SK_ColorWHITE; | |
| 28 | |
| 29 const int kShadowOffsetX = 0; | |
| 30 const int kShadowOffsetY = 5; | |
| 31 const double kShadowBlur = 30; | |
| 32 const SkColor kShadowColor = SkColorSetARGB(0x72, 0, 0, 0); | |
| 33 | |
| 34 // Builds a bubble shape for given |bounds|. | |
| 35 void BuildShape(const gfx::Rect& bounds, | |
| 36 views::BubbleBorder::ArrowLocation arrow_location, | |
| 37 SkScalar arrow_offset, | |
| 38 SkScalar padding, | |
| 39 SkPath* path, | |
| 40 int corner_radius_int, | |
| 41 int arrow_height_int, | |
| 42 int arrow_width_int) { | |
| 43 const SkScalar corner_radius = SkIntToScalar(corner_radius_int); | |
| 44 | |
| 45 const SkScalar left = SkIntToScalar(bounds.x()) + padding; | |
| 46 const SkScalar top = SkIntToScalar(bounds.y()) + padding; | |
| 47 const SkScalar right = SkIntToScalar(bounds.right()) - padding; | |
| 48 const SkScalar bottom = SkIntToScalar(bounds.bottom()) - padding; | |
| 49 | |
| 50 const SkScalar center_x = SkIntToScalar((bounds.x() + bounds.right()) / 2); | |
| 51 const SkScalar center_y = SkIntToScalar((bounds.y() + bounds.bottom()) / 2); | |
| 52 | |
| 53 const SkScalar half_arrow_width = | |
| 54 (SkIntToScalar(arrow_width_int) - padding) / 2; | |
| 55 const SkScalar arrow_height = SkIntToScalar(arrow_height_int) - padding; | |
| 56 | |
| 57 path->reset(); | |
| 58 path->incReserve(12); | |
| 59 | |
| 60 switch (arrow_location) { | |
| 61 case views::BubbleBorder::TOP_LEFT: | |
| 62 case views::BubbleBorder::TOP_RIGHT: | |
| 63 path->moveTo(center_x, bottom); | |
| 64 path->arcTo(right, bottom, right, center_y, corner_radius); | |
| 65 path->arcTo(right, top, center_x - half_arrow_width, top, | |
| 66 corner_radius); | |
| 67 path->lineTo(center_x + arrow_offset + half_arrow_width, top); | |
| 68 path->lineTo(center_x + arrow_offset, top - arrow_height); | |
| 69 path->lineTo(center_x + arrow_offset - half_arrow_width, top); | |
| 70 path->arcTo(left, top, left, center_y, corner_radius); | |
| 71 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
| 72 break; | |
| 73 case views::BubbleBorder::BOTTOM_LEFT: | |
| 74 case views::BubbleBorder::BOTTOM_RIGHT: | |
| 75 path->moveTo(center_x, top); | |
| 76 path->arcTo(left, top, left, center_y, corner_radius); | |
| 77 path->arcTo(left, bottom, center_x - half_arrow_width, bottom, | |
| 78 corner_radius); | |
| 79 path->lineTo(center_x + arrow_offset - half_arrow_width, bottom); | |
| 80 path->lineTo(center_x + arrow_offset, bottom + arrow_height); | |
| 81 path->lineTo(center_x + arrow_offset + half_arrow_width, bottom); | |
| 82 path->arcTo(right, bottom, right, center_y, corner_radius); | |
| 83 path->arcTo(right, top, center_x, top, corner_radius); | |
| 84 break; | |
| 85 case views::BubbleBorder::LEFT_TOP: | |
| 86 case views::BubbleBorder::LEFT_BOTTOM: | |
| 87 path->moveTo(right, center_y); | |
| 88 path->arcTo(right, top, center_x, top, corner_radius); | |
| 89 path->arcTo(left, top, left, center_y + arrow_offset - half_arrow_width, | |
| 90 corner_radius); | |
| 91 path->lineTo(left, center_y + arrow_offset - half_arrow_width); | |
| 92 path->lineTo(left - arrow_height, center_y + arrow_offset); | |
| 93 path->lineTo(left, center_y + arrow_offset + half_arrow_width); | |
| 94 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
| 95 path->arcTo(right, bottom, right, center_y, corner_radius); | |
| 96 break; | |
| 97 case views::BubbleBorder::RIGHT_TOP: | |
| 98 case views::BubbleBorder::RIGHT_BOTTOM: | |
| 99 path->moveTo(left, center_y); | |
| 100 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
| 101 path->arcTo(right, bottom, | |
| 102 right, center_y + arrow_offset + half_arrow_width, | |
| 103 corner_radius); | |
| 104 path->lineTo(right, center_y + arrow_offset + half_arrow_width); | |
| 105 path->lineTo(right + arrow_height, center_y + arrow_offset); | |
| 106 path->lineTo(right, center_y + arrow_offset - half_arrow_width); | |
| 107 path->arcTo(right, top, center_x, top, corner_radius); | |
| 108 path->arcTo(left, top, left, center_y, corner_radius); | |
| 109 break; | |
| 110 default: | |
| 111 // No arrows. | |
| 112 path->addRoundRect(gfx::RectToSkRect(bounds), | |
| 113 corner_radius, | |
| 114 corner_radius); | |
| 115 break; | |
| 116 } | |
| 117 | |
| 118 path->close(); | |
| 119 } | |
| 120 | |
| 121 } // namespace | |
| 122 | |
| 123 namespace views { | |
| 124 | |
| 125 BubbleBorder2::BubbleBorder2(ArrowLocation arrow_location) | |
| 126 : BubbleBorder(arrow_location, views::BubbleBorder::NO_SHADOW), | |
| 127 corner_radius_(kCornerRadius), | |
| 128 border_size_(kBorderSize), | |
| 129 arrow_height_(kArrowHeight), | |
| 130 arrow_width_(kArrowWidth), | |
| 131 background_color_(kBackgroundColor), | |
| 132 border_color_(kBorderColor) { | |
| 133 SetShadow(gfx::ShadowValue(gfx::Point(kShadowOffsetX, kShadowOffsetY), | |
| 134 kShadowBlur, kShadowColor)); | |
| 135 } | |
| 136 | |
| 137 BubbleBorder2::~BubbleBorder2() {} | |
| 138 | |
| 139 gfx::Rect BubbleBorder2::ComputeOffsetAndUpdateBubbleRect( | |
| 140 gfx::Rect bubble_rect, | |
| 141 const gfx::Rect& anchor_view_rect) { | |
| 142 offset_ = gfx::Point(); | |
| 143 | |
| 144 gfx::Rect monitor_rect = gfx::Screen::GetDisplayNearestPoint( | |
| 145 anchor_view_rect.CenterPoint()).bounds(); | |
| 146 if (monitor_rect.IsEmpty() || monitor_rect.Contains(bubble_rect)) | |
| 147 return bubble_rect; | |
| 148 | |
| 149 gfx::Point offset; | |
| 150 | |
| 151 if (has_arrow(arrow_location())) { | |
| 152 if (is_arrow_on_horizontal(arrow_location())) { | |
| 153 if (bubble_rect.x() < monitor_rect.x()) | |
| 154 offset.set_x(monitor_rect.x() - bubble_rect.x()); | |
| 155 else if (bubble_rect.right() > monitor_rect.right()) | |
| 156 offset.set_x(monitor_rect.right() - bubble_rect.right()); | |
| 157 } else { | |
| 158 if (bubble_rect.y() < monitor_rect.y()) | |
| 159 offset.set_y(monitor_rect.y() - bubble_rect.y()); | |
| 160 else if (bubble_rect.bottom() > monitor_rect.bottom()) | |
| 161 offset.set_y(monitor_rect.bottom() - bubble_rect.bottom()); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 bubble_rect.Offset(offset); | |
| 166 set_offset(offset); | |
| 167 | |
| 168 return bubble_rect; | |
| 169 } | |
| 170 | |
| 171 void BubbleBorder2::GetMask(const gfx::Rect& bounds, | |
| 172 gfx::Path* mask) const { | |
| 173 gfx::Insets insets; | |
| 174 GetInsets(&insets); | |
| 175 | |
| 176 gfx::Rect content_bounds(bounds); | |
| 177 content_bounds.Inset(insets); | |
| 178 | |
| 179 BuildShape(content_bounds, | |
| 180 arrow_location(), | |
| 181 SkIntToScalar(GetArrowOffset()), | |
| 182 SkIntToScalar(kBorderSize), | |
| 183 mask, | |
| 184 corner_radius_, | |
| 185 arrow_height_, | |
| 186 arrow_width_); | |
| 187 } | |
| 188 | |
| 189 void BubbleBorder2::SetShadow(gfx::ShadowValue shadow) { | |
| 190 shadows_.clear(); | |
| 191 shadows_.push_back(shadow); | |
| 192 } | |
| 193 | |
| 194 int BubbleBorder2::GetBorderThickness() const { | |
| 195 return border_size_; | |
| 196 } | |
| 197 | |
| 198 void BubbleBorder2::PaintBackground(gfx::Canvas* canvas, | |
| 199 const gfx::Rect& bounds) const { | |
| 200 canvas->FillRect(bounds, background_color_); | |
| 201 } | |
| 202 | |
| 203 int BubbleBorder2::GetArrowOffset() const { | |
| 204 if (has_arrow(arrow_location())) { | |
| 205 if (is_arrow_on_horizontal(arrow_location())) { | |
| 206 // Picks x offset and moves bubble arrow in the opposite direction. | |
| 207 // i.e. If bubble bounds is moved to right (positive offset), we need to | |
| 208 // move arrow to left so that it points to the same position. | |
| 209 return -offset_.x(); | |
| 210 } else { | |
| 211 // Picks y offset and moves bubble arrow in the opposite direction. | |
| 212 return -offset_.y(); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 // Other style does not have an arrow, so return 0. | |
| 217 return 0; | |
| 218 } | |
| 219 | |
| 220 void BubbleBorder2::GetInsets(gfx::Insets* insets) const { | |
| 221 // Negate to change from outer margin to inner padding. | |
| 222 gfx::Insets shadow_padding(-gfx::ShadowValue::GetMargin(shadows_)); | |
| 223 | |
| 224 if (arrow_location() == views::BubbleBorder::TOP_LEFT || | |
| 225 arrow_location() == views::BubbleBorder::TOP_RIGHT) { | |
| 226 // Arrow at top. | |
| 227 insets->Set(shadow_padding.top() + arrow_height_, | |
| 228 shadow_padding.left(), | |
| 229 shadow_padding.bottom(), | |
| 230 shadow_padding.right()); | |
| 231 } else if (arrow_location() == views::BubbleBorder::BOTTOM_LEFT || | |
| 232 arrow_location() == views::BubbleBorder::BOTTOM_RIGHT) { | |
| 233 // Arrow at bottom. | |
| 234 insets->Set(shadow_padding.top(), | |
| 235 shadow_padding.left(), | |
| 236 shadow_padding.bottom() + arrow_height_, | |
| 237 shadow_padding.right()); | |
| 238 } else if (arrow_location() == views::BubbleBorder::LEFT_TOP || | |
| 239 arrow_location() == views::BubbleBorder::LEFT_BOTTOM) { | |
| 240 // Arrow on left. | |
| 241 insets->Set(shadow_padding.top(), | |
| 242 shadow_padding.left() + arrow_height_, | |
| 243 shadow_padding.bottom(), | |
| 244 shadow_padding.right()); | |
| 245 } else if (arrow_location() == views::BubbleBorder::RIGHT_TOP || | |
| 246 arrow_location() == views::BubbleBorder::RIGHT_BOTTOM) { | |
| 247 // Arrow on right. | |
| 248 insets->Set(shadow_padding.top(), | |
| 249 shadow_padding.left(), | |
| 250 shadow_padding.bottom(), | |
| 251 shadow_padding.right() + arrow_height_); | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 gfx::Rect BubbleBorder2::GetBounds(const gfx::Rect& position_relative_to, | |
| 256 const gfx::Size& contents_size) const { | |
| 257 gfx::Size border_size(contents_size); | |
| 258 gfx::Insets insets; | |
| 259 GetInsets(&insets); | |
| 260 border_size.Enlarge(insets.width(), insets.height()); | |
| 261 | |
| 262 // Negate to change from outer margin to inner padding. | |
| 263 gfx::Insets shadow_padding(-gfx::ShadowValue::GetMargin(shadows_)); | |
| 264 | |
| 265 // Anchor center that arrow aligns with. | |
| 266 const int anchor_center_x = | |
| 267 (position_relative_to.x() + position_relative_to.right()) / 2; | |
| 268 const int anchor_center_y = | |
| 269 (position_relative_to.y() + position_relative_to.bottom()) / 2; | |
| 270 | |
| 271 // Arrow position relative to top-left of bubble. |arrow_tip_x| is used for | |
| 272 // arrow at the top or bottom and |arrow_tip_y| is used for arrow on left or | |
| 273 // right. The 1px offset for |arrow_tip_y| is needed because the app list grid | |
| 274 // icon start at a different position (1px earlier) compared with bottom | |
| 275 // launcher bar. | |
| 276 // TODO(xiyuan): Remove 1px offset when app list icon image asset is updated. | |
| 277 int arrow_tip_x = insets.left() + contents_size.width() / 2 + | |
| 278 GetArrowOffset(); | |
| 279 int arrow_tip_y = insets.top() + contents_size.height() / 2 + | |
| 280 GetArrowOffset() + 1; | |
| 281 | |
| 282 if (arrow_location() == views::BubbleBorder::TOP_LEFT || | |
| 283 arrow_location() == views::BubbleBorder::TOP_RIGHT) { | |
| 284 // Arrow at top. | |
| 285 return gfx::Rect( | |
| 286 gfx::Point(anchor_center_x - arrow_tip_x, | |
| 287 position_relative_to.bottom() - shadow_padding.top()), | |
| 288 border_size); | |
| 289 } else if (arrow_location() == views::BubbleBorder::BOTTOM_LEFT || | |
| 290 arrow_location() == views::BubbleBorder::BOTTOM_RIGHT) { | |
| 291 // Arrow at bottom. | |
| 292 return gfx::Rect( | |
| 293 gfx::Point(anchor_center_x - arrow_tip_x, | |
| 294 position_relative_to.y() - border_size.height() + | |
| 295 shadow_padding.bottom()), | |
| 296 border_size); | |
| 297 } else if (arrow_location() == views::BubbleBorder::LEFT_TOP || | |
| 298 arrow_location() == views::BubbleBorder::LEFT_BOTTOM) { | |
| 299 // Arrow on left. | |
| 300 return gfx::Rect( | |
| 301 gfx::Point(position_relative_to.right() - shadow_padding.left(), | |
| 302 anchor_center_y - arrow_tip_y), | |
| 303 border_size); | |
| 304 } else if (arrow_location() == views::BubbleBorder::RIGHT_TOP || | |
| 305 arrow_location() == views::BubbleBorder::RIGHT_BOTTOM) { | |
| 306 // Arrow on right. | |
| 307 return gfx::Rect( | |
| 308 gfx::Point(position_relative_to.x() - border_size.width() + | |
| 309 shadow_padding.right(), | |
| 310 anchor_center_y - arrow_tip_y), | |
| 311 border_size); | |
| 312 } | |
| 313 | |
| 314 // No arrow bubble, center align with anchor. | |
| 315 return position_relative_to.Center(border_size); | |
| 316 } | |
| 317 | |
| 318 void BubbleBorder2::GetInsetsForArrowLocation(gfx::Insets* insets, | |
| 319 ArrowLocation arrow_loc) const { | |
| 320 int top = border_size_; | |
| 321 int bottom = border_size_; | |
| 322 int left = border_size_; | |
| 323 int right = border_size_; | |
| 324 switch (arrow_loc) { | |
| 325 case TOP_LEFT: | |
| 326 case TOP_RIGHT: | |
| 327 top = std::max(top, arrow_height_); | |
| 328 break; | |
| 329 | |
| 330 case BOTTOM_LEFT: | |
| 331 case BOTTOM_RIGHT: | |
| 332 bottom = std::max(bottom, arrow_height_); | |
| 333 break; | |
| 334 | |
| 335 case LEFT_TOP: | |
| 336 case LEFT_BOTTOM: | |
| 337 left = std::max(left, arrow_height_); | |
| 338 break; | |
| 339 | |
| 340 case RIGHT_TOP: | |
| 341 case RIGHT_BOTTOM: | |
| 342 right = std::max(right, arrow_height_); | |
| 343 break; | |
| 344 | |
| 345 case NONE: | |
| 346 case FLOAT: | |
| 347 // Nothing to do. | |
| 348 break; | |
| 349 } | |
| 350 insets->Set(top, left, bottom, right); | |
| 351 } | |
| 352 | |
| 353 void BubbleBorder2::Paint(const views::View& view, gfx::Canvas* canvas) const { | |
| 354 gfx::Insets insets; | |
| 355 GetInsets(&insets); | |
| 356 | |
| 357 gfx::Rect content_bounds = view.bounds(); | |
| 358 content_bounds.Inset(insets); | |
| 359 | |
| 360 SkPath path; | |
| 361 // Pads with 0.5 pixel since anti alias is used. | |
| 362 BuildShape(content_bounds, | |
| 363 arrow_location(), | |
| 364 SkIntToScalar(GetArrowOffset()), | |
| 365 SkDoubleToScalar(0.5), | |
| 366 &path, | |
| 367 corner_radius_, | |
| 368 arrow_height_, | |
| 369 arrow_width_); | |
| 370 | |
| 371 // Draw border and shadow. Note fill is needed to generate enough shadow. | |
| 372 SkPaint paint; | |
| 373 paint.setAntiAlias(true); | |
| 374 paint.setStyle(SkPaint::kStrokeAndFill_Style); | |
| 375 paint.setStrokeWidth(SkIntToScalar(border_size_)); | |
| 376 paint.setColor(border_color_); | |
| 377 SkSafeUnref(paint.setLooper(gfx::CreateShadowDrawLooper(shadows_))); | |
| 378 canvas->DrawPath(path, paint); | |
| 379 | |
| 380 // Pads with |border_size_| pixels to leave space for border lines. | |
| 381 BuildShape(content_bounds, | |
| 382 arrow_location(), | |
| 383 SkIntToScalar(GetArrowOffset()), | |
| 384 SkIntToScalar(border_size_), | |
| 385 &path, | |
| 386 corner_radius_, | |
| 387 arrow_height_, | |
| 388 arrow_width_); | |
| 389 canvas->Save(); | |
| 390 canvas->ClipPath(path); | |
| 391 | |
| 392 // Use full bounds so that arrow is also painted. | |
| 393 const gfx::Rect& bounds = view.bounds(); | |
| 394 PaintBackground(canvas, bounds); | |
| 395 | |
| 396 canvas->Restore(); | |
| 397 } | |
| 398 | |
| 399 } // namespace views | |
| OLD | NEW |