OLD | NEW |
| (Empty) |
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 "views/bubble/bubble_border.h" | |
6 | |
7 #include <algorithm> // for std::max | |
8 | |
9 #include "base/logging.h" | |
10 #include "grit/ui_resources.h" | |
11 #include "grit/ui_resources_standard.h" | |
12 #include "third_party/skia/include/core/SkBitmap.h" | |
13 #include "ui/base/resource/resource_bundle.h" | |
14 #include "ui/gfx/canvas_skia.h" | |
15 #include "ui/gfx/path.h" | |
16 | |
17 namespace views { | |
18 | |
19 struct BubbleBorder::BorderImages { | |
20 BorderImages() | |
21 : left(NULL), | |
22 top_left(NULL), | |
23 top(NULL), | |
24 top_right(NULL), | |
25 right(NULL), | |
26 bottom_right(NULL), | |
27 bottom(NULL), | |
28 bottom_left(NULL), | |
29 left_arrow(NULL), | |
30 top_arrow(NULL), | |
31 right_arrow(NULL), | |
32 bottom_arrow(NULL) { | |
33 } | |
34 | |
35 SkBitmap* left; | |
36 SkBitmap* top_left; | |
37 SkBitmap* top; | |
38 SkBitmap* top_right; | |
39 SkBitmap* right; | |
40 SkBitmap* bottom_right; | |
41 SkBitmap* bottom; | |
42 SkBitmap* bottom_left; | |
43 SkBitmap* left_arrow; | |
44 SkBitmap* top_arrow; | |
45 SkBitmap* right_arrow; | |
46 SkBitmap* bottom_arrow; | |
47 }; | |
48 | |
49 // static | |
50 struct BubbleBorder::BorderImages* BubbleBorder::normal_images_ = NULL; | |
51 struct BubbleBorder::BorderImages* BubbleBorder::shadow_images_ = NULL; | |
52 | |
53 | |
54 // The height inside the arrow image, in pixels. | |
55 static const int kArrowInteriorHeight = 7; | |
56 | |
57 BubbleBorder::BubbleBorder(ArrowLocation arrow_location, Shadow shadow) | |
58 : override_arrow_offset_(0), | |
59 arrow_location_(arrow_location), | |
60 alignment_(ALIGN_ARROW_TO_MID_ANCHOR), | |
61 background_color_(SK_ColorWHITE) { | |
62 images_ = GetBorderImages(shadow); | |
63 | |
64 // Calculate horizontal and vertical insets for arrow by ensuring that | |
65 // the widest arrow and corner images will have enough room to avoid overlap | |
66 int offset_x = | |
67 (std::max(images_->top_arrow->width(), | |
68 images_->bottom_arrow->width()) / 2) + | |
69 std::max(std::max(images_->top_left->width(), | |
70 images_->top_right->width()), | |
71 std::max(images_->bottom_left->width(), | |
72 images_->bottom_right->width())); | |
73 int offset_y = | |
74 (std::max(images_->left_arrow->height(), | |
75 images_->right_arrow->height()) / 2) + | |
76 std::max(std::max(images_->top_left->height(), | |
77 images_->top_right->height()), | |
78 std::max(images_->bottom_left->height(), | |
79 images_->bottom_right->height())); | |
80 arrow_offset_ = std::max(offset_x, offset_y); | |
81 } | |
82 | |
83 gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& position_relative_to, | |
84 const gfx::Size& contents_size) const { | |
85 // Desired size is size of contents enlarged by the size of the border images. | |
86 gfx::Size border_size(contents_size); | |
87 gfx::Insets insets; | |
88 GetInsets(&insets); | |
89 border_size.Enlarge(insets.left() + insets.right(), | |
90 insets.top() + insets.bottom()); | |
91 | |
92 // Screen position depends on the arrow location. | |
93 // The arrow should overlap the target by some amount since there is space | |
94 // for shadow between arrow tip and bitmap bounds. | |
95 const int kArrowOverlap = 3; | |
96 int x = position_relative_to.x(); | |
97 int y = position_relative_to.y(); | |
98 int w = position_relative_to.width(); | |
99 int h = position_relative_to.height(); | |
100 int arrow_offset = override_arrow_offset_ ? override_arrow_offset_ : | |
101 arrow_offset_; | |
102 | |
103 // Calculate bubble x coordinate. | |
104 switch (arrow_location_) { | |
105 case TOP_LEFT: | |
106 case BOTTOM_LEFT: | |
107 x += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? w / 2 - arrow_offset : | |
108 -kArrowOverlap; | |
109 break; | |
110 | |
111 case TOP_RIGHT: | |
112 case BOTTOM_RIGHT: | |
113 x += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? | |
114 w / 2 + arrow_offset - border_size.width() + 1 : | |
115 w - border_size.width() + kArrowOverlap; | |
116 break; | |
117 | |
118 case LEFT_TOP: | |
119 case LEFT_BOTTOM: | |
120 x += w - kArrowOverlap; | |
121 break; | |
122 | |
123 case RIGHT_TOP: | |
124 case RIGHT_BOTTOM: | |
125 x += kArrowOverlap - border_size.width(); | |
126 break; | |
127 | |
128 case NONE: | |
129 case FLOAT: | |
130 x += w / 2 - border_size.width() / 2; | |
131 break; | |
132 } | |
133 | |
134 // Calculate bubble y coordinate. | |
135 switch (arrow_location_) { | |
136 case TOP_LEFT: | |
137 case TOP_RIGHT: | |
138 y += h - kArrowOverlap; | |
139 break; | |
140 | |
141 case BOTTOM_LEFT: | |
142 case BOTTOM_RIGHT: | |
143 y += kArrowOverlap - border_size.height(); | |
144 break; | |
145 | |
146 case LEFT_TOP: | |
147 case RIGHT_TOP: | |
148 y += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? h / 2 - arrow_offset : | |
149 -kArrowOverlap; | |
150 break; | |
151 | |
152 case LEFT_BOTTOM: | |
153 case RIGHT_BOTTOM: | |
154 y += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? | |
155 h / 2 + arrow_offset - border_size.height() + 1 : | |
156 h - border_size.height() + kArrowOverlap; | |
157 break; | |
158 | |
159 case NONE: | |
160 y += h; | |
161 break; | |
162 | |
163 case FLOAT: | |
164 y += h / 2 - border_size.height() / 2; | |
165 break; | |
166 } | |
167 | |
168 return gfx::Rect(x, y, border_size.width(), border_size.height()); | |
169 } | |
170 | |
171 void BubbleBorder::GetInsets(gfx::Insets* insets) const { | |
172 int top = images_->top->height(); | |
173 int bottom = images_->bottom->height(); | |
174 int left = images_->left->width(); | |
175 int right = images_->right->width(); | |
176 switch (arrow_location_) { | |
177 case TOP_LEFT: | |
178 case TOP_RIGHT: | |
179 top = std::max(top, images_->top_arrow->height()); | |
180 break; | |
181 | |
182 case BOTTOM_LEFT: | |
183 case BOTTOM_RIGHT: | |
184 bottom = std::max(bottom, images_->bottom_arrow->height()); | |
185 break; | |
186 | |
187 case LEFT_TOP: | |
188 case LEFT_BOTTOM: | |
189 left = std::max(left, images_->left_arrow->width()); | |
190 break; | |
191 | |
192 case RIGHT_TOP: | |
193 case RIGHT_BOTTOM: | |
194 right = std::max(right, images_->right_arrow->width()); | |
195 break; | |
196 | |
197 case NONE: | |
198 case FLOAT: | |
199 // Nothing to do. | |
200 break; | |
201 } | |
202 insets->Set(top, left, bottom, right); | |
203 } | |
204 | |
205 int BubbleBorder::SetArrowOffset(int offset, const gfx::Size& contents_size) { | |
206 gfx::Size border_size(contents_size); | |
207 gfx::Insets insets; | |
208 GetInsets(&insets); | |
209 border_size.Enlarge(insets.left() + insets.right(), | |
210 insets.top() + insets.bottom()); | |
211 offset = std::max(arrow_offset_, | |
212 std::min(offset, (is_arrow_on_horizontal(arrow_location_) ? | |
213 border_size.width() : border_size.height()) - arrow_offset_)); | |
214 override_arrow_offset_ = offset; | |
215 return override_arrow_offset_; | |
216 } | |
217 | |
218 // static | |
219 BubbleBorder::BorderImages* BubbleBorder::GetBorderImages(Shadow shadow) { | |
220 if (shadow == SHADOW && shadow_images_ == NULL) { | |
221 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
222 shadow_images_ = new BorderImages(); | |
223 shadow_images_->left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_L); | |
224 shadow_images_->top_left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_TL); | |
225 shadow_images_->top = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_T); | |
226 shadow_images_->top_right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_TR); | |
227 shadow_images_->right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_R); | |
228 shadow_images_->bottom_right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_BR); | |
229 shadow_images_->bottom = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_B); | |
230 shadow_images_->bottom_left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_BL); | |
231 shadow_images_->left_arrow = new SkBitmap(); | |
232 shadow_images_->top_arrow = new SkBitmap(); | |
233 shadow_images_->right_arrow = new SkBitmap(); | |
234 shadow_images_->bottom_arrow = new SkBitmap(); | |
235 } else if (shadow == NO_SHADOW && normal_images_ == NULL) { | |
236 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
237 normal_images_ = new BorderImages(); | |
238 normal_images_->left = rb.GetBitmapNamed(IDR_BUBBLE_L); | |
239 normal_images_->top_left = rb.GetBitmapNamed(IDR_BUBBLE_TL); | |
240 normal_images_->top = rb.GetBitmapNamed(IDR_BUBBLE_T); | |
241 normal_images_->top_right = rb.GetBitmapNamed(IDR_BUBBLE_TR); | |
242 normal_images_->right = rb.GetBitmapNamed(IDR_BUBBLE_R); | |
243 normal_images_->bottom_right = rb.GetBitmapNamed(IDR_BUBBLE_BR); | |
244 normal_images_->bottom = rb.GetBitmapNamed(IDR_BUBBLE_B); | |
245 normal_images_->bottom_left = rb.GetBitmapNamed(IDR_BUBBLE_BL); | |
246 normal_images_->left_arrow = rb.GetBitmapNamed(IDR_BUBBLE_L_ARROW); | |
247 normal_images_->top_arrow = rb.GetBitmapNamed(IDR_BUBBLE_T_ARROW); | |
248 normal_images_->right_arrow = rb.GetBitmapNamed(IDR_BUBBLE_R_ARROW); | |
249 normal_images_->bottom_arrow = rb.GetBitmapNamed(IDR_BUBBLE_B_ARROW); | |
250 } | |
251 return shadow == SHADOW ? shadow_images_ : normal_images_; | |
252 } | |
253 | |
254 BubbleBorder::~BubbleBorder() {} | |
255 | |
256 void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) const { | |
257 // Convenience shorthand variables. | |
258 const int tl_width = images_->top_left->width(); | |
259 const int tl_height = images_->top_left->height(); | |
260 const int t_height = images_->top->height(); | |
261 const int tr_width = images_->top_right->width(); | |
262 const int tr_height = images_->top_right->height(); | |
263 const int l_width = images_->left->width(); | |
264 const int r_width = images_->right->width(); | |
265 const int br_width = images_->bottom_right->width(); | |
266 const int br_height = images_->bottom_right->height(); | |
267 const int b_height = images_->bottom->height(); | |
268 const int bl_width = images_->bottom_left->width(); | |
269 const int bl_height = images_->bottom_left->height(); | |
270 | |
271 gfx::Insets insets; | |
272 GetInsets(&insets); | |
273 const int top = insets.top() - t_height; | |
274 const int bottom = view.height() - insets.bottom() + b_height; | |
275 const int left = insets.left() - l_width; | |
276 const int right = view.width() - insets.right() + r_width; | |
277 const int height = bottom - top; | |
278 const int width = right - left; | |
279 | |
280 // |arrow_offset| is offset of arrow from the begining of the edge. | |
281 int arrow_offset = arrow_offset_; | |
282 if (override_arrow_offset_) | |
283 arrow_offset = override_arrow_offset_; | |
284 else if (is_arrow_on_horizontal(arrow_location_) && | |
285 !is_arrow_on_left(arrow_location_)) { | |
286 arrow_offset = view.width() - arrow_offset - 1; | |
287 } else if (!is_arrow_on_horizontal(arrow_location_) && | |
288 !is_arrow_on_top(arrow_location_)) { | |
289 arrow_offset = view.height() - arrow_offset - 1; | |
290 } | |
291 | |
292 // Left edge. | |
293 if (arrow_location_ == LEFT_TOP || arrow_location_ == LEFT_BOTTOM) { | |
294 int start_y = top + tl_height; | |
295 int before_arrow = | |
296 arrow_offset - start_y - images_->left_arrow->height() / 2; | |
297 int after_arrow = height - tl_height - bl_height - | |
298 images_->left_arrow->height() - before_arrow; | |
299 int tip_y = start_y + before_arrow + images_->left_arrow->height() / 2; | |
300 DrawArrowInterior(canvas, | |
301 false, | |
302 images_->left_arrow->width() - kArrowInteriorHeight, | |
303 tip_y, | |
304 kArrowInteriorHeight, | |
305 images_->left_arrow->height() / 2 - 1); | |
306 DrawEdgeWithArrow(canvas, | |
307 false, | |
308 images_->left, | |
309 images_->left_arrow, | |
310 left, | |
311 start_y, | |
312 before_arrow, | |
313 after_arrow, | |
314 images_->left->width() - images_->left_arrow->width()); | |
315 } else { | |
316 canvas->TileImageInt(*images_->left, left, top + tl_height, l_width, | |
317 height - tl_height - bl_height); | |
318 } | |
319 | |
320 // Top left corner. | |
321 canvas->DrawBitmapInt(*images_->top_left, left, top); | |
322 | |
323 // Top edge. | |
324 if (arrow_location_ == TOP_LEFT || arrow_location_ == TOP_RIGHT) { | |
325 int start_x = left + tl_width; | |
326 int before_arrow = arrow_offset - start_x - images_->top_arrow->width() / 2; | |
327 int after_arrow = width - tl_width - tr_width - | |
328 images_->top_arrow->width() - before_arrow; | |
329 DrawArrowInterior(canvas, | |
330 true, | |
331 start_x + before_arrow + images_->top_arrow->width() / 2, | |
332 images_->top_arrow->height() - kArrowInteriorHeight, | |
333 1 - images_->top_arrow->width() / 2, | |
334 kArrowInteriorHeight); | |
335 DrawEdgeWithArrow(canvas, | |
336 true, | |
337 images_->top, | |
338 images_->top_arrow, | |
339 start_x, | |
340 top, | |
341 before_arrow, | |
342 after_arrow, | |
343 images_->top->height() - images_->top_arrow->height()); | |
344 } else { | |
345 canvas->TileImageInt(*images_->top, left + tl_width, top, | |
346 width - tl_width - tr_width, t_height); | |
347 } | |
348 | |
349 // Top right corner. | |
350 canvas->DrawBitmapInt(*images_->top_right, right - tr_width, top); | |
351 | |
352 // Right edge. | |
353 if (arrow_location_ == RIGHT_TOP || arrow_location_ == RIGHT_BOTTOM) { | |
354 int start_y = top + tr_height; | |
355 int before_arrow = | |
356 arrow_offset - start_y - images_->right_arrow->height() / 2; | |
357 int after_arrow = height - tl_height - bl_height - | |
358 images_->right_arrow->height() - before_arrow; | |
359 int tip_y = start_y + before_arrow + images_->right_arrow->height() / 2; | |
360 DrawArrowInterior(canvas, | |
361 false, | |
362 right - r_width + kArrowInteriorHeight, | |
363 tip_y, | |
364 -kArrowInteriorHeight, | |
365 images_->right_arrow->height() / 2 - 1); | |
366 DrawEdgeWithArrow(canvas, | |
367 false, | |
368 images_->right, | |
369 images_->right_arrow, | |
370 right - r_width, | |
371 start_y, | |
372 before_arrow, | |
373 after_arrow, | |
374 0); | |
375 } else { | |
376 canvas->TileImageInt(*images_->right, right - r_width, top + tr_height, | |
377 r_width, height - tr_height - br_height); | |
378 } | |
379 | |
380 // Bottom right corner. | |
381 canvas->DrawBitmapInt(*images_->bottom_right, | |
382 right - br_width, | |
383 bottom - br_height); | |
384 | |
385 // Bottom edge. | |
386 if (arrow_location_ == BOTTOM_LEFT || arrow_location_ == BOTTOM_RIGHT) { | |
387 int start_x = left + bl_width; | |
388 int before_arrow = | |
389 arrow_offset - start_x - images_->bottom_arrow->width() / 2; | |
390 int after_arrow = width - bl_width - br_width - | |
391 images_->bottom_arrow->width() - before_arrow; | |
392 int tip_x = start_x + before_arrow + images_->bottom_arrow->width() / 2; | |
393 DrawArrowInterior(canvas, | |
394 true, | |
395 tip_x, | |
396 bottom - b_height + kArrowInteriorHeight, | |
397 1 - images_->bottom_arrow->width() / 2, | |
398 -kArrowInteriorHeight); | |
399 DrawEdgeWithArrow(canvas, | |
400 true, | |
401 images_->bottom, | |
402 images_->bottom_arrow, | |
403 start_x, | |
404 bottom - b_height, | |
405 before_arrow, | |
406 after_arrow, | |
407 0); | |
408 } else { | |
409 canvas->TileImageInt(*images_->bottom, left + bl_width, bottom - b_height, | |
410 width - bl_width - br_width, b_height); | |
411 } | |
412 | |
413 // Bottom left corner. | |
414 canvas->DrawBitmapInt(*images_->bottom_left, left, bottom - bl_height); | |
415 } | |
416 | |
417 void BubbleBorder::DrawEdgeWithArrow(gfx::Canvas* canvas, | |
418 bool is_horizontal, | |
419 SkBitmap* edge, | |
420 SkBitmap* arrow, | |
421 int start_x, | |
422 int start_y, | |
423 int before_arrow, | |
424 int after_arrow, | |
425 int offset) const { | |
426 /* Here's what the parameters mean: | |
427 * start_x | |
428 * . | |
429 * . ┌───┐ ┬ offset | |
430 * start_y..........┌────┬────────┤ ▲ ├────────┬────┐ | |
431 * │ / │--------│∙ ∙│--------│ \ │ | |
432 * │ / ├────────┴───┴────────┤ \ │ | |
433 * ├───┬┘ └┬───┤ | |
434 * └───┬────┘ └───┬────┘ | |
435 * before_arrow ─┘ └─ after_arrow | |
436 */ | |
437 if (before_arrow) { | |
438 canvas->TileImageInt(*edge, start_x, start_y, | |
439 is_horizontal ? before_arrow : edge->width(), | |
440 is_horizontal ? edge->height() : before_arrow); | |
441 } | |
442 | |
443 canvas->DrawBitmapInt(*arrow, | |
444 start_x + (is_horizontal ? before_arrow : offset), | |
445 start_y + (is_horizontal ? offset : before_arrow)); | |
446 | |
447 if (after_arrow) { | |
448 start_x += (is_horizontal ? before_arrow + arrow->width() : 0); | |
449 start_y += (is_horizontal ? 0 : before_arrow + arrow->height()); | |
450 canvas->TileImageInt(*edge, start_x, start_y, | |
451 is_horizontal ? after_arrow : edge->width(), | |
452 is_horizontal ? edge->height() : after_arrow); | |
453 } | |
454 } | |
455 | |
456 void BubbleBorder::DrawArrowInterior(gfx::Canvas* canvas, | |
457 bool is_horizontal, | |
458 int tip_x, | |
459 int tip_y, | |
460 int shift_x, | |
461 int shift_y) const { | |
462 /* This function fills the interior of the arrow with background color. | |
463 * It draws isosceles triangle under semitransparent arrow tip. | |
464 * | |
465 * Here's what the parameters mean: | |
466 * | |
467 * ┌──────── |tip_x| | |
468 * ┌─────┐ | |
469 * │ ▲ │ ──── |tip y| | |
470 * │∙∙∙∙∙│ ┐ | |
471 * └─────┘ └─── |shift_x| (offset from tip to vertexes of isosceles triangle) | |
472 * └────────── |shift_y| | |
473 */ | |
474 SkPaint paint; | |
475 paint.setStyle(SkPaint::kFill_Style); | |
476 paint.setColor(background_color_); | |
477 gfx::Path path; | |
478 path.incReserve(4); | |
479 path.moveTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); | |
480 path.lineTo(SkIntToScalar(tip_x + shift_x), | |
481 SkIntToScalar(tip_y + shift_y)); | |
482 if (is_horizontal) | |
483 path.lineTo(SkIntToScalar(tip_x - shift_x), SkIntToScalar(tip_y + shift_y)); | |
484 else | |
485 path.lineTo(SkIntToScalar(tip_x + shift_x), SkIntToScalar(tip_y - shift_y)); | |
486 path.close(); | |
487 canvas->GetSkCanvas()->drawPath(path, paint); | |
488 } | |
489 | |
490 ///////////////////////// | |
491 | |
492 void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const { | |
493 // The border of this view creates an anti-aliased round-rect region for the | |
494 // contents, which we need to fill with the background color. | |
495 // NOTE: This doesn't handle an arrow location of "NONE", which has square top | |
496 // corners. | |
497 SkPaint paint; | |
498 paint.setAntiAlias(true); | |
499 paint.setStyle(SkPaint::kFill_Style); | |
500 paint.setColor(border_->background_color()); | |
501 gfx::Path path; | |
502 gfx::Rect bounds(view->GetContentsBounds()); | |
503 SkRect rect; | |
504 rect.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()), | |
505 SkIntToScalar(bounds.right()), SkIntToScalar(bounds.bottom())); | |
506 SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius()); | |
507 path.addRoundRect(rect, radius, radius); | |
508 canvas->GetSkCanvas()->drawPath(path, paint); | |
509 } | |
510 | |
511 } // namespace views | |
OLD | NEW |