OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/app_list/app_list_bubble_border.h" | 5 #include "ui/app_list/app_list_bubble_border.h" |
6 | 6 |
7 #include "third_party/skia/include/core/SkPath.h" | 7 #include "third_party/skia/include/core/SkPath.h" |
8 #include "third_party/skia/include/core/SkPaint.h" | 8 #include "third_party/skia/include/core/SkPaint.h" |
9 #include "third_party/skia/include/effects/SkGradientShader.h" | 9 #include "third_party/skia/include/effects/SkGradientShader.h" |
10 #include "ui/gfx/canvas.h" | 10 #include "ui/gfx/canvas.h" |
11 #include "ui/gfx/path.h" | 11 #include "ui/gfx/path.h" |
12 #include "ui/gfx/skia_util.h" | 12 #include "ui/gfx/skia_util.h" |
13 | 13 |
14 namespace { | 14 namespace { |
15 | 15 |
16 // Bubble border corner radius. | |
17 const int kCornerRadius = 2; | |
18 | |
19 // Arrow width and height. | |
20 const int kArrowHeight = 10; | |
21 const int kArrowWidth = 20; | |
22 | |
23 // Bubble border color and width. | |
24 const SkColor kBorderColor = SkColorSetARGB(0x26, 0, 0, 0); | |
25 const int kBorderSize = 1; | |
26 | |
27 const SkColor kSearchBoxBackground = SK_ColorWHITE; | 16 const SkColor kSearchBoxBackground = SK_ColorWHITE; |
28 const SkColor kContentsBackground = SkColorSetRGB(0xFC, 0xFC, 0xFC); | 17 const SkColor kContentsBackground = SkColorSetRGB(0xFC, 0xFC, 0xFC); |
29 | 18 |
30 // Colors and sizes of top separator between searchbox and grid view. | 19 // Colors and sizes of top separator between searchbox and grid view. |
31 const SkColor kTopSeparatorColor = SkColorSetRGB(0xF0, 0xF0, 0xF0); | 20 const SkColor kTopSeparatorColor = SkColorSetRGB(0xF0, 0xF0, 0xF0); |
32 const int kTopSeparatorSize = 1; | 21 const int kTopSeparatorSize = 1; |
33 | 22 |
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 const SkScalar corner_radius = SkIntToScalar(kCornerRadius); | |
41 | |
42 const SkScalar left = SkIntToScalar(bounds.x()) + padding; | |
43 const SkScalar top = SkIntToScalar(bounds.y()) + padding; | |
44 const SkScalar right = SkIntToScalar(bounds.right()) - padding; | |
45 const SkScalar bottom = SkIntToScalar(bounds.bottom()) - padding; | |
46 | |
47 const SkScalar center_x = SkIntToScalar((bounds.x() + bounds.right()) / 2); | |
48 const SkScalar center_y = SkIntToScalar((bounds.y() + bounds.bottom()) / 2); | |
49 | |
50 const SkScalar half_arrow_width = (SkIntToScalar(kArrowWidth) - padding) / 2; | |
51 const SkScalar arrow_height = SkIntToScalar(kArrowHeight) - padding; | |
52 | |
53 path->reset(); | |
54 path->incReserve(12); | |
55 | |
56 switch (arrow_location) { | |
57 case views::BubbleBorder::TOP_LEFT: | |
58 case views::BubbleBorder::TOP_RIGHT: | |
59 path->moveTo(center_x, bottom); | |
60 path->arcTo(right, bottom, right, center_y, corner_radius); | |
61 path->arcTo(right, top, center_x - half_arrow_width, top, | |
62 corner_radius); | |
63 path->lineTo(center_x + arrow_offset + half_arrow_width, top); | |
64 path->lineTo(center_x + arrow_offset, top - arrow_height); | |
65 path->lineTo(center_x + arrow_offset - half_arrow_width, top); | |
66 path->arcTo(left, top, left, center_y, corner_radius); | |
67 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
68 break; | |
69 case views::BubbleBorder::BOTTOM_LEFT: | |
70 case views::BubbleBorder::BOTTOM_RIGHT: | |
71 path->moveTo(center_x, top); | |
72 path->arcTo(left, top, left, center_y, corner_radius); | |
73 path->arcTo(left, bottom, center_x - half_arrow_width, bottom, | |
74 corner_radius); | |
75 path->lineTo(center_x + arrow_offset - half_arrow_width, bottom); | |
76 path->lineTo(center_x + arrow_offset, bottom + arrow_height); | |
77 path->lineTo(center_x + arrow_offset + half_arrow_width, bottom); | |
78 path->arcTo(right, bottom, right, center_y, corner_radius); | |
79 path->arcTo(right, top, center_x, top, corner_radius); | |
80 break; | |
81 case views::BubbleBorder::LEFT_TOP: | |
82 case views::BubbleBorder::LEFT_BOTTOM: | |
83 path->moveTo(right, center_y); | |
84 path->arcTo(right, top, center_x, top, corner_radius); | |
85 path->arcTo(left, top, left, center_y + arrow_offset - half_arrow_width, | |
86 corner_radius); | |
87 path->lineTo(left, center_y + arrow_offset - half_arrow_width); | |
88 path->lineTo(left - arrow_height, center_y + arrow_offset); | |
89 path->lineTo(left, center_y + arrow_offset + half_arrow_width); | |
90 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
91 path->arcTo(right, bottom, right, center_y, corner_radius); | |
92 break; | |
93 case views::BubbleBorder::RIGHT_TOP: | |
94 case views::BubbleBorder::RIGHT_BOTTOM: | |
95 path->moveTo(left, center_y); | |
96 path->arcTo(left, bottom, center_x, bottom, corner_radius); | |
97 path->arcTo(right, bottom, | |
98 right, center_y + arrow_offset + half_arrow_width, | |
99 corner_radius); | |
100 path->lineTo(right, center_y + arrow_offset + half_arrow_width); | |
101 path->lineTo(right + arrow_height, center_y + arrow_offset); | |
102 path->lineTo(right, center_y + arrow_offset - half_arrow_width); | |
103 path->arcTo(right, top, center_x, top, corner_radius); | |
104 path->arcTo(left, top, left, center_y, corner_radius); | |
105 break; | |
106 default: | |
107 // No arrows. | |
108 path->addRoundRect(gfx::RectToSkRect(bounds), | |
109 corner_radius, | |
110 corner_radius); | |
111 break; | |
112 } | |
113 | |
114 path->close(); | |
115 } | |
116 | |
117 } // namespace | 23 } // namespace |
118 | 24 |
119 namespace app_list { | 25 namespace app_list { |
120 | 26 |
121 AppListBubbleBorder::AppListBubbleBorder(views::View* app_list_view, | 27 AppListBubbleBorder::AppListBubbleBorder(views::View* app_list_view, |
122 views::View* search_box_view) | 28 views::View* search_box_view) |
123 : views::BubbleBorder(views::BubbleBorder::BOTTOM_RIGHT, | 29 : views::BubbleBorderGeometric(views::BubbleBorder::BOTTOM_RIGHT), |
124 views::BubbleBorder::NO_SHADOW), | |
125 app_list_view_(app_list_view), | 30 app_list_view_(app_list_view), |
126 search_box_view_(search_box_view) { | 31 search_box_view_(search_box_view) { |
127 const gfx::ShadowValue kShadows[] = { | |
128 // Offset (0, 5), blur=30, color=0.36 black | |
129 gfx::ShadowValue(gfx::Point(0, 5), 30, SkColorSetARGB(0x72, 0, 0, 0)), | |
130 }; | |
131 shadows_.assign(kShadows, kShadows + arraysize(kShadows)); | |
132 } | 32 } |
133 | 33 |
134 AppListBubbleBorder::~AppListBubbleBorder() { | 34 AppListBubbleBorder::~AppListBubbleBorder() { |
135 } | 35 } |
136 | 36 |
137 bool AppListBubbleBorder::ArrowAtTopOrBottom() const { | |
138 return arrow_location() == views::BubbleBorder::TOP_LEFT || | |
139 arrow_location() == views::BubbleBorder::TOP_RIGHT || | |
140 arrow_location() == views::BubbleBorder::BOTTOM_LEFT || | |
141 arrow_location() == views::BubbleBorder::BOTTOM_RIGHT; | |
142 } | |
143 | |
144 bool AppListBubbleBorder::ArrowOnLeftOrRight() const { | |
145 return arrow_location() == views::BubbleBorder::LEFT_TOP || | |
146 arrow_location() == views::BubbleBorder::LEFT_BOTTOM || | |
147 arrow_location() == views::BubbleBorder::RIGHT_TOP || | |
148 arrow_location() == views::BubbleBorder::RIGHT_BOTTOM; | |
149 } | |
150 | |
151 void AppListBubbleBorder::GetMask(const gfx::Rect& bounds, | |
152 gfx::Path* mask) const { | |
153 gfx::Insets insets; | |
154 GetInsets(&insets); | |
155 | |
156 gfx::Rect content_bounds(bounds); | |
157 content_bounds.Inset(insets); | |
158 | |
159 BuildShape(content_bounds, | |
160 arrow_location(), | |
161 SkIntToScalar(GetArrowOffset()), | |
162 SkIntToScalar(kBorderSize), | |
163 mask); | |
164 } | |
165 | |
166 int AppListBubbleBorder::GetArrowOffset() const { | |
167 if (ArrowAtTopOrBottom()) { | |
168 // Picks x offset and moves bubble arrow in the opposite direction. | |
169 // i.e. If bubble bounds is moved to right (positive offset), we need to | |
170 // move arrow to left so that it points to the same position. | |
171 return -offset_.x(); | |
172 } else if (ArrowOnLeftOrRight()) { | |
173 // Picks y offset and moves bubble arrow in the opposite direction. | |
174 return -offset_.y(); | |
175 } | |
176 | |
177 // Other style does not have an arrow, so return 0. | |
178 return 0; | |
179 } | |
180 | |
181 void AppListBubbleBorder::PaintBackground(gfx::Canvas* canvas, | 37 void AppListBubbleBorder::PaintBackground(gfx::Canvas* canvas, |
182 const gfx::Rect& bounds) const { | 38 const gfx::Rect& bounds) const { |
183 const gfx::Rect search_box_view_bounds = | 39 const gfx::Rect search_box_view_bounds = |
184 app_list_view_->ConvertRectToWidget(search_box_view_->bounds()); | 40 app_list_view_->ConvertRectToWidget(search_box_view_->bounds()); |
185 gfx::Rect search_box_rect(bounds.x(), | 41 gfx::Rect search_box_rect(bounds.x(), |
186 bounds.y(), | 42 bounds.y(), |
187 bounds.width(), | 43 bounds.width(), |
188 search_box_view_bounds.bottom() - bounds.y()); | 44 search_box_view_bounds.bottom() - bounds.y()); |
189 | 45 |
190 SkPaint paint; | 46 SkPaint paint; |
191 paint.setStyle(SkPaint::kFill_Style); | 47 paint.setStyle(SkPaint::kFill_Style); |
192 paint.setColor(kSearchBoxBackground); | 48 paint.setColor(kSearchBoxBackground); |
193 canvas->DrawRect(search_box_rect, paint); | 49 canvas->DrawRect(search_box_rect, paint); |
194 | 50 |
195 gfx::Rect seperator_rect(search_box_rect); | 51 gfx::Rect seperator_rect(search_box_rect); |
196 seperator_rect.set_y(seperator_rect.bottom()); | 52 seperator_rect.set_y(seperator_rect.bottom()); |
197 seperator_rect.set_height(kTopSeparatorSize); | 53 seperator_rect.set_height(kTopSeparatorSize); |
198 canvas->FillRect(seperator_rect, kTopSeparatorColor); | 54 canvas->FillRect(seperator_rect, kTopSeparatorColor); |
199 | 55 |
200 gfx::Rect contents_rect(bounds.x(), | 56 gfx::Rect contents_rect(bounds.x(), |
201 seperator_rect.bottom(), | 57 seperator_rect.bottom(), |
202 bounds.width(), | 58 bounds.width(), |
203 bounds.bottom() - seperator_rect.bottom()); | 59 bounds.bottom() - seperator_rect.bottom()); |
204 | 60 |
205 paint.setColor(kContentsBackground); | 61 paint.setColor(kContentsBackground); |
206 canvas->DrawRect(contents_rect, paint); | 62 canvas->DrawRect(contents_rect, paint); |
207 } | 63 } |
208 | 64 |
209 void AppListBubbleBorder::GetInsets(gfx::Insets* insets) const { | |
210 // Negate to change from outer margin to inner padding. | |
211 gfx::Insets shadow_padding(-gfx::ShadowValue::GetMargin(shadows_)); | |
212 | |
213 if (arrow_location() == views::BubbleBorder::TOP_LEFT || | |
214 arrow_location() == views::BubbleBorder::TOP_RIGHT) { | |
215 // Arrow at top. | |
216 insets->Set(shadow_padding.top() + kArrowHeight, | |
217 shadow_padding.left(), | |
218 shadow_padding.bottom(), | |
219 shadow_padding.right()); | |
220 } else if (arrow_location() == views::BubbleBorder::BOTTOM_LEFT || | |
221 arrow_location() == views::BubbleBorder::BOTTOM_RIGHT) { | |
222 // Arrow at bottom. | |
223 insets->Set(shadow_padding.top(), | |
224 shadow_padding.left(), | |
225 shadow_padding.bottom() + kArrowHeight, | |
226 shadow_padding.right()); | |
227 } else if (arrow_location() == views::BubbleBorder::LEFT_TOP || | |
228 arrow_location() == views::BubbleBorder::LEFT_BOTTOM) { | |
229 // Arrow on left. | |
230 insets->Set(shadow_padding.top(), | |
231 shadow_padding.left() + kArrowHeight, | |
232 shadow_padding.bottom(), | |
233 shadow_padding.right()); | |
234 } else if (arrow_location() == views::BubbleBorder::RIGHT_TOP || | |
235 arrow_location() == views::BubbleBorder::RIGHT_BOTTOM) { | |
236 // Arrow on right. | |
237 insets->Set(shadow_padding.top(), | |
238 shadow_padding.left(), | |
239 shadow_padding.bottom(), | |
240 shadow_padding.right() + kArrowHeight); | |
241 } | |
242 } | |
243 | |
244 gfx::Rect AppListBubbleBorder::GetBounds( | |
245 const gfx::Rect& position_relative_to, | |
246 const gfx::Size& contents_size) const { | |
247 gfx::Size border_size(contents_size); | |
248 gfx::Insets insets; | |
249 GetInsets(&insets); | |
250 border_size.Enlarge(insets.width(), insets.height()); | |
251 | |
252 // Negate to change from outer margin to inner padding. | |
253 gfx::Insets shadow_padding(-gfx::ShadowValue::GetMargin(shadows_)); | |
254 | |
255 // Anchor center that arrow aligns with. | |
256 const int anchor_center_x = | |
257 (position_relative_to.x() + position_relative_to.right()) / 2; | |
258 const int anchor_center_y = | |
259 (position_relative_to.y() + position_relative_to.bottom()) / 2; | |
260 | |
261 // Arrow position relative to top-left of bubble. |arrow_tip_x| is used for | |
262 // arrow at the top or bottom and |arrow_tip_y| is used for arrow on left or | |
263 // right. The 1px offset for |arrow_tip_y| is needed because the app list grid | |
264 // icon start at a different position (1px earlier) compared with bottom | |
265 // launcher bar. | |
266 // TODO(xiyuan): Remove 1px offset when app list icon image asset is updated. | |
267 int arrow_tip_x = insets.left() + contents_size.width() / 2 + | |
268 GetArrowOffset(); | |
269 int arrow_tip_y = insets.top() + contents_size.height() / 2 + | |
270 GetArrowOffset() + 1; | |
271 | |
272 if (arrow_location() == views::BubbleBorder::TOP_LEFT || | |
273 arrow_location() == views::BubbleBorder::TOP_RIGHT) { | |
274 // Arrow at top. | |
275 return gfx::Rect( | |
276 gfx::Point(anchor_center_x - arrow_tip_x, | |
277 position_relative_to.bottom() - shadow_padding.top() - | |
278 kArrowHeight), | |
279 border_size); | |
280 } else if (arrow_location() == views::BubbleBorder::BOTTOM_LEFT || | |
281 arrow_location() == views::BubbleBorder::BOTTOM_RIGHT) { | |
282 // Arrow at bottom. | |
283 return gfx::Rect( | |
284 gfx::Point(anchor_center_x - arrow_tip_x, | |
285 position_relative_to.y() - border_size.height() + | |
286 shadow_padding.bottom() + kArrowHeight), | |
287 border_size); | |
288 } else if (arrow_location() == views::BubbleBorder::LEFT_TOP || | |
289 arrow_location() == views::BubbleBorder::LEFT_BOTTOM) { | |
290 // Arrow on left. | |
291 return gfx::Rect( | |
292 gfx::Point(position_relative_to.right() - shadow_padding.left() - | |
293 kArrowHeight, | |
294 anchor_center_y - arrow_tip_y), | |
295 border_size); | |
296 } else if (arrow_location() == views::BubbleBorder::RIGHT_TOP || | |
297 arrow_location() == views::BubbleBorder::RIGHT_BOTTOM) { | |
298 // Arrow on right. | |
299 return gfx::Rect( | |
300 gfx::Point(position_relative_to.x() - border_size.width() + | |
301 shadow_padding.right() + kArrowHeight, | |
302 anchor_center_y - arrow_tip_y), | |
303 border_size); | |
304 } | |
305 | |
306 // No arrow bubble, center align with anchor. | |
307 return position_relative_to.Center(border_size); | |
308 } | |
309 | |
310 void AppListBubbleBorder::Paint(const views::View& view, | |
311 gfx::Canvas* canvas) const { | |
312 gfx::Insets insets; | |
313 GetInsets(&insets); | |
314 | |
315 gfx::Rect content_bounds = view.bounds(); | |
316 content_bounds.Inset(insets); | |
317 | |
318 SkPath path; | |
319 // Pads with 0.5 pixel since anti alias is used. | |
320 BuildShape(content_bounds, | |
321 arrow_location(), | |
322 SkIntToScalar(GetArrowOffset()), | |
323 SkDoubleToScalar(0.5), | |
324 &path); | |
325 | |
326 // Draw border and shadow. Note fill is needed to generate enough shadow. | |
327 SkPaint paint; | |
328 paint.setAntiAlias(true); | |
329 paint.setStyle(SkPaint::kStrokeAndFill_Style); | |
330 paint.setStrokeWidth(SkIntToScalar(kBorderSize)); | |
331 paint.setColor(kBorderColor); | |
332 SkSafeUnref(paint.setLooper(gfx::CreateShadowDrawLooper(shadows_))); | |
333 canvas->DrawPath(path, paint); | |
334 | |
335 // Pads with kBoprderSize pixels to leave space for border lines. | |
336 BuildShape(content_bounds, | |
337 arrow_location(), | |
338 SkIntToScalar(GetArrowOffset()), | |
339 SkIntToScalar(kBorderSize), | |
340 &path); | |
341 canvas->Save(); | |
342 canvas->ClipPath(path); | |
343 | |
344 // Use full bounds so that arrow is also painted. | |
345 const gfx::Rect& bounds = view.bounds(); | |
346 PaintBackground(canvas, bounds); | |
347 | |
348 canvas->Restore(); | |
349 } | |
350 | |
351 } // namespace app_list | 65 } // namespace app_list |
OLD | NEW |