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 "gfx/native_theme_win.h" | |
6 | |
7 #include <windows.h> | |
8 #include <uxtheme.h> | |
9 #include <vsstyle.h> | |
10 #include <vssym32.h> | |
11 | |
12 #include "base/logging.h" | |
13 #include "base/scoped_handle.h" | |
14 #include "base/win/scoped_gdi_object.h" | |
15 #include "base/win/scoped_hdc.h" | |
16 #include "base/win/windows_version.h" | |
17 #include "gfx/gdi_util.h" | |
18 #include "gfx/rect.h" | |
19 #include "skia/ext/platform_canvas.h" | |
20 #include "skia/ext/skia_utils_win.h" | |
21 #include "third_party/skia/include/core/SkShader.h" | |
22 | |
23 namespace { | |
24 | |
25 void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) { | |
26 // Create a 2x2 checkerboard pattern using the 3D face and highlight colors. | |
27 SkColor face = skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE)); | |
28 SkColor highlight = skia::COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT)); | |
29 SkColor buffer[] = { face, highlight, highlight, face }; | |
30 // Confusing bit: we first create a temporary bitmap with our desired pattern, | |
31 // then copy it to another bitmap. The temporary bitmap doesn't take | |
32 // ownership of the pixel data, and so will point to garbage when this | |
33 // function returns. The copy will copy the pixel data into a place owned by | |
34 // the bitmap, which is in turn owned by the shader, etc., so it will live | |
35 // until we're done using it. | |
36 SkBitmap temp_bitmap; | |
37 temp_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); | |
38 temp_bitmap.setPixels(buffer); | |
39 SkBitmap bitmap; | |
40 temp_bitmap.copyTo(&bitmap, temp_bitmap.config()); | |
41 SkShader* shader = SkShader::CreateBitmapShader(bitmap, | |
42 SkShader::kRepeat_TileMode, | |
43 SkShader::kRepeat_TileMode); | |
44 | |
45 // Align the pattern with the upper corner of |align_rect|. | |
46 SkMatrix matrix; | |
47 matrix.setTranslate(SkIntToScalar(align_rect.left), | |
48 SkIntToScalar(align_rect.top)); | |
49 shader->setLocalMatrix(matrix); | |
50 SkSafeUnref(paint->setShader(shader)); | |
51 } | |
52 | |
53 } // namespace | |
54 | |
55 namespace gfx { | |
56 | |
57 /* static */ | |
58 const NativeTheme* NativeTheme::instance() { | |
59 // The global NativeTheme instance. | |
60 static const NativeTheme s_native_theme; | |
61 return &s_native_theme; | |
62 } | |
63 | |
64 NativeTheme::NativeTheme() | |
65 : theme_dll_(LoadLibrary(L"uxtheme.dll")), | |
66 draw_theme_(NULL), | |
67 draw_theme_ex_(NULL), | |
68 get_theme_color_(NULL), | |
69 get_theme_content_rect_(NULL), | |
70 get_theme_part_size_(NULL), | |
71 open_theme_(NULL), | |
72 close_theme_(NULL), | |
73 set_theme_properties_(NULL), | |
74 is_theme_active_(NULL), | |
75 get_theme_int_(NULL) { | |
76 if (theme_dll_) { | |
77 draw_theme_ = reinterpret_cast<DrawThemeBackgroundPtr>( | |
78 GetProcAddress(theme_dll_, "DrawThemeBackground")); | |
79 draw_theme_ex_ = reinterpret_cast<DrawThemeBackgroundExPtr>( | |
80 GetProcAddress(theme_dll_, "DrawThemeBackgroundEx")); | |
81 get_theme_color_ = reinterpret_cast<GetThemeColorPtr>( | |
82 GetProcAddress(theme_dll_, "GetThemeColor")); | |
83 get_theme_content_rect_ = reinterpret_cast<GetThemeContentRectPtr>( | |
84 GetProcAddress(theme_dll_, "GetThemeBackgroundContentRect")); | |
85 get_theme_part_size_ = reinterpret_cast<GetThemePartSizePtr>( | |
86 GetProcAddress(theme_dll_, "GetThemePartSize")); | |
87 open_theme_ = reinterpret_cast<OpenThemeDataPtr>( | |
88 GetProcAddress(theme_dll_, "OpenThemeData")); | |
89 close_theme_ = reinterpret_cast<CloseThemeDataPtr>( | |
90 GetProcAddress(theme_dll_, "CloseThemeData")); | |
91 set_theme_properties_ = reinterpret_cast<SetThemeAppPropertiesPtr>( | |
92 GetProcAddress(theme_dll_, "SetThemeAppProperties")); | |
93 is_theme_active_ = reinterpret_cast<IsThemeActivePtr>( | |
94 GetProcAddress(theme_dll_, "IsThemeActive")); | |
95 get_theme_int_ = reinterpret_cast<GetThemeIntPtr>( | |
96 GetProcAddress(theme_dll_, "GetThemeInt")); | |
97 } | |
98 memset(theme_handles_, 0, sizeof(theme_handles_)); | |
99 } | |
100 | |
101 NativeTheme::~NativeTheme() { | |
102 if (theme_dll_) { | |
103 // todo (cpu): fix this soon. | |
104 // CloseHandles(); | |
105 FreeLibrary(theme_dll_); | |
106 } | |
107 } | |
108 | |
109 HRESULT NativeTheme::PaintButton(HDC hdc, | |
110 int part_id, | |
111 int state_id, | |
112 int classic_state, | |
113 RECT* rect) const { | |
114 HANDLE handle = GetThemeHandle(BUTTON); | |
115 if (handle && draw_theme_) | |
116 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
117 | |
118 // Draw it manually. | |
119 // All pressed states have both low bits set, and no other states do. | |
120 const bool focused = ((state_id & ETS_FOCUSED) == ETS_FOCUSED); | |
121 const bool pressed = ((state_id & PBS_PRESSED) == PBS_PRESSED); | |
122 if ((BP_PUSHBUTTON == part_id) && (pressed || focused)) { | |
123 // BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the | |
124 // button itself is shrunk by 1 pixel. | |
125 HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW); | |
126 if (brush) { | |
127 FrameRect(hdc, rect, brush); | |
128 InflateRect(rect, -1, -1); | |
129 } | |
130 } | |
131 DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state); | |
132 | |
133 // Draw the focus rectangle (the dotted line box) only on buttons. For radio | |
134 // and checkboxes, we let webkit draw the focus rectangle (orange glow). | |
135 if ((BP_PUSHBUTTON == part_id) && focused) { | |
136 // The focus rect is inside the button. The exact number of pixels depends | |
137 // on whether we're in classic mode or using uxtheme. | |
138 if (handle && get_theme_content_rect_) { | |
139 get_theme_content_rect_(handle, hdc, part_id, state_id, rect, rect); | |
140 } else { | |
141 InflateRect(rect, -GetSystemMetrics(SM_CXEDGE), | |
142 -GetSystemMetrics(SM_CYEDGE)); | |
143 } | |
144 DrawFocusRect(hdc, rect); | |
145 } | |
146 | |
147 return S_OK; | |
148 } | |
149 | |
150 HRESULT NativeTheme::PaintDialogBackground(HDC hdc, bool active, | |
151 RECT* rect) const { | |
152 HANDLE handle = GetThemeHandle(WINDOW); | |
153 if (handle && draw_theme_) { | |
154 return draw_theme_(handle, hdc, WP_DIALOG, | |
155 active ? FS_ACTIVE : FS_INACTIVE, rect, NULL); | |
156 } | |
157 | |
158 // Classic just renders a flat color background. | |
159 FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1)); | |
160 return S_OK; | |
161 } | |
162 | |
163 HRESULT NativeTheme::PaintListBackground(HDC hdc, | |
164 bool enabled, | |
165 RECT* rect) const { | |
166 HANDLE handle = GetThemeHandle(LIST); | |
167 if (handle && draw_theme_) | |
168 return draw_theme_(handle, hdc, 1, TS_NORMAL, rect, NULL); | |
169 | |
170 // Draw it manually. | |
171 HBRUSH bg_brush = GetSysColorBrush(COLOR_WINDOW); | |
172 FillRect(hdc, rect, bg_brush); | |
173 DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); | |
174 return S_OK; | |
175 } | |
176 | |
177 HRESULT NativeTheme::PaintMenuArrow(ThemeName theme, | |
178 HDC hdc, | |
179 int part_id, | |
180 int state_id, | |
181 RECT* rect, | |
182 MenuArrowDirection arrow_direction, | |
183 ControlState control_state) const { | |
184 HANDLE handle = GetThemeHandle(MENU); | |
185 if (handle && draw_theme_) { | |
186 if (arrow_direction == RIGHT_POINTING_ARROW) { | |
187 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
188 } else { | |
189 // There is no way to tell the uxtheme API to draw a left pointing arrow; | |
190 // it doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they | |
191 // are needed for RTL locales on Vista. So use a memory DC and mirror | |
192 // the region with GDI's StretchBlt. | |
193 Rect r(*rect); | |
194 base::win::ScopedHDC mem_dc(CreateCompatibleDC(hdc)); | |
195 base::win::ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(), | |
196 r.height())); | |
197 HGDIOBJ old_bitmap = SelectObject(mem_dc, mem_bitmap); | |
198 // Copy and horizontally mirror the background from hdc into mem_dc. Use | |
199 // a negative-width source rect, starting at the rightmost pixel. | |
200 StretchBlt(mem_dc, 0, 0, r.width(), r.height(), | |
201 hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY); | |
202 // Draw the arrow. | |
203 RECT theme_rect = {0, 0, r.width(), r.height()}; | |
204 HRESULT result = draw_theme_(handle, mem_dc, part_id, | |
205 state_id, &theme_rect, NULL); | |
206 // Copy and mirror the result back into mem_dc. | |
207 StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(), | |
208 mem_dc, r.width()-1, 0, -r.width(), r.height(), SRCCOPY); | |
209 SelectObject(mem_dc, old_bitmap); | |
210 return result; | |
211 } | |
212 } | |
213 | |
214 // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a | |
215 // left pointing arrow. This makes the following 'if' statement slightly | |
216 // counterintuitive. | |
217 UINT state; | |
218 if (arrow_direction == RIGHT_POINTING_ARROW) | |
219 state = DFCS_MENUARROW; | |
220 else | |
221 state = DFCS_MENUARROWRIGHT; | |
222 return PaintFrameControl(hdc, rect, DFC_MENU, state, control_state); | |
223 } | |
224 | |
225 HRESULT NativeTheme::PaintMenuBackground(ThemeName theme, | |
226 HDC hdc, | |
227 int part_id, | |
228 int state_id, | |
229 RECT* rect) const { | |
230 HANDLE handle = GetThemeHandle(MENU); | |
231 if (handle && draw_theme_) { | |
232 HRESULT result = draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
233 FrameRect(hdc, rect, GetSysColorBrush(COLOR_3DSHADOW)); | |
234 return result; | |
235 } | |
236 | |
237 FillRect(hdc, rect, GetSysColorBrush(COLOR_MENU)); | |
238 DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT); | |
239 return S_OK; | |
240 } | |
241 | |
242 HRESULT NativeTheme::PaintMenuCheckBackground(ThemeName theme, | |
243 HDC hdc, | |
244 int part_id, | |
245 int state_id, | |
246 RECT* rect) const { | |
247 HANDLE handle = GetThemeHandle(MENU); | |
248 if (handle && draw_theme_) | |
249 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
250 // Nothing to do for background. | |
251 return S_OK; | |
252 } | |
253 | |
254 HRESULT NativeTheme::PaintMenuCheck(ThemeName theme, | |
255 HDC hdc, | |
256 int part_id, | |
257 int state_id, | |
258 RECT* rect, | |
259 ControlState control_state) const { | |
260 HANDLE handle = GetThemeHandle(MENU); | |
261 if (handle && draw_theme_) { | |
262 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
263 } | |
264 return PaintFrameControl(hdc, rect, DFC_MENU, DFCS_MENUCHECK, control_state); | |
265 } | |
266 | |
267 HRESULT NativeTheme::PaintMenuGutter(HDC hdc, | |
268 int part_id, | |
269 int state_id, | |
270 RECT* rect) const { | |
271 HANDLE handle = GetThemeHandle(MENU); | |
272 if (handle && draw_theme_) | |
273 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
274 return E_NOTIMPL; | |
275 } | |
276 | |
277 HRESULT NativeTheme::PaintMenuItemBackground(ThemeName theme, | |
278 HDC hdc, | |
279 int part_id, | |
280 int state_id, | |
281 bool selected, | |
282 RECT* rect) const { | |
283 HANDLE handle = GetThemeHandle(MENU); | |
284 if (handle && draw_theme_) | |
285 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
286 if (selected) | |
287 FillRect(hdc, rect, GetSysColorBrush(COLOR_HIGHLIGHT)); | |
288 return S_OK; | |
289 } | |
290 | |
291 HRESULT NativeTheme::PaintMenuList(HDC hdc, | |
292 int part_id, | |
293 int state_id, | |
294 int classic_state, | |
295 RECT* rect) const { | |
296 HANDLE handle = GetThemeHandle(MENULIST); | |
297 if (handle && draw_theme_) | |
298 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
299 | |
300 // Draw it manually. | |
301 DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX | classic_state); | |
302 return S_OK; | |
303 } | |
304 | |
305 HRESULT NativeTheme::PaintMenuSeparator(HDC hdc, | |
306 int part_id, | |
307 int state_id, | |
308 RECT* rect) const { | |
309 HANDLE handle = GetThemeHandle(MENU); | |
310 if (handle && draw_theme_) | |
311 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
312 DrawEdge(hdc, rect, EDGE_ETCHED, BF_TOP); | |
313 return S_OK; | |
314 } | |
315 | |
316 HRESULT NativeTheme::PaintScrollbarArrow(HDC hdc, | |
317 int state_id, | |
318 int classic_state, | |
319 RECT* rect) const { | |
320 HANDLE handle = GetThemeHandle(SCROLLBAR); | |
321 if (handle && draw_theme_) | |
322 return draw_theme_(handle, hdc, SBP_ARROWBTN, state_id, rect, NULL); | |
323 | |
324 // Draw it manually. | |
325 DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state); | |
326 return S_OK; | |
327 } | |
328 | |
329 HRESULT NativeTheme::PaintScrollbarTrack( | |
330 HDC hdc, | |
331 int part_id, | |
332 int state_id, | |
333 int classic_state, | |
334 RECT* target_rect, | |
335 RECT* align_rect, | |
336 skia::PlatformCanvas* canvas) const { | |
337 HANDLE handle = GetThemeHandle(SCROLLBAR); | |
338 if (handle && draw_theme_) | |
339 return draw_theme_(handle, hdc, part_id, state_id, target_rect, NULL); | |
340 | |
341 // Draw it manually. | |
342 const DWORD colorScrollbar = GetSysColor(COLOR_SCROLLBAR); | |
343 const DWORD color3DFace = GetSysColor(COLOR_3DFACE); | |
344 if ((colorScrollbar != color3DFace) && | |
345 (colorScrollbar != GetSysColor(COLOR_WINDOW))) { | |
346 FillRect(hdc, target_rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1)); | |
347 } else { | |
348 SkPaint paint; | |
349 SetCheckerboardShader(&paint, *align_rect); | |
350 canvas->drawIRect(skia::RECTToSkIRect(*target_rect), paint); | |
351 } | |
352 if (classic_state & DFCS_PUSHED) | |
353 InvertRect(hdc, target_rect); | |
354 return S_OK; | |
355 } | |
356 | |
357 HRESULT NativeTheme::PaintScrollbarThumb(HDC hdc, | |
358 int part_id, | |
359 int state_id, | |
360 int classic_state, | |
361 RECT* rect) const { | |
362 HANDLE handle = GetThemeHandle(SCROLLBAR); | |
363 if (handle && draw_theme_) | |
364 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
365 | |
366 // Draw it manually. | |
367 if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT)) | |
368 DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT | BF_MIDDLE); | |
369 // Classic mode doesn't have a gripper. | |
370 return S_OK; | |
371 } | |
372 | |
373 HRESULT NativeTheme::PaintSpinButton(HDC hdc, | |
374 int part_id, | |
375 int state_id, | |
376 int classic_state, | |
377 RECT* rect) const { | |
378 HANDLE handle = GetThemeHandle(SPIN); | |
379 if (handle && draw_theme_) | |
380 return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
381 DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state); | |
382 return S_OK; | |
383 } | |
384 | |
385 HRESULT NativeTheme::PaintStatusGripper(HDC hdc, | |
386 int part_id, | |
387 int state_id, | |
388 int classic_state, | |
389 RECT* rect) const { | |
390 HANDLE handle = GetThemeHandle(STATUS); | |
391 if (handle && draw_theme_) { | |
392 // Paint the status bar gripper. There doesn't seem to be a | |
393 // standard gripper in Windows for the space between | |
394 // scrollbars. This is pretty close, but it's supposed to be | |
395 // painted over a status bar. | |
396 return draw_theme_(handle, hdc, SP_GRIPPER, 0, rect, NULL); | |
397 } | |
398 | |
399 // Draw a windows classic scrollbar gripper. | |
400 DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); | |
401 return S_OK; | |
402 } | |
403 | |
404 HRESULT NativeTheme::PaintTabPanelBackground(HDC hdc, RECT* rect) const { | |
405 HANDLE handle = GetThemeHandle(TAB); | |
406 if (handle && draw_theme_) | |
407 return draw_theme_(handle, hdc, TABP_BODY, 0, rect, NULL); | |
408 | |
409 // Classic just renders a flat color background. | |
410 FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1)); | |
411 return S_OK; | |
412 } | |
413 | |
414 HRESULT NativeTheme::PaintTrackbar(HDC hdc, | |
415 int part_id, | |
416 int state_id, | |
417 int classic_state, | |
418 RECT* rect, | |
419 skia::PlatformCanvas* canvas) const { | |
420 // Make the channel be 4 px thick in the center of the supplied rect. (4 px | |
421 // matches what XP does in various menus; GetThemePartSize() doesn't seem to | |
422 // return good values here.) | |
423 RECT channel_rect = *rect; | |
424 const int channel_thickness = 4; | |
425 if (part_id == TKP_TRACK) { | |
426 channel_rect.top += | |
427 ((channel_rect.bottom - channel_rect.top - channel_thickness) / 2); | |
428 channel_rect.bottom = channel_rect.top + channel_thickness; | |
429 } else if (part_id == TKP_TRACKVERT) { | |
430 channel_rect.left += | |
431 ((channel_rect.right - channel_rect.left - channel_thickness) / 2); | |
432 channel_rect.right = channel_rect.left + channel_thickness; | |
433 } // else this isn't actually a channel, so |channel_rect| == |rect|. | |
434 | |
435 HANDLE handle = GetThemeHandle(TRACKBAR); | |
436 if (handle && draw_theme_) | |
437 return draw_theme_(handle, hdc, part_id, state_id, &channel_rect, NULL); | |
438 | |
439 // Classic mode, draw it manually. | |
440 if ((part_id == TKP_TRACK) || (part_id == TKP_TRACKVERT)) { | |
441 DrawEdge(hdc, &channel_rect, EDGE_SUNKEN, BF_RECT); | |
442 } else if (part_id == TKP_THUMBVERT) { | |
443 DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE); | |
444 } else { | |
445 // Split rect into top and bottom pieces. | |
446 RECT top_section = *rect; | |
447 RECT bottom_section = *rect; | |
448 top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2); | |
449 bottom_section.top = top_section.bottom; | |
450 DrawEdge(hdc, &top_section, EDGE_RAISED, | |
451 BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST); | |
452 | |
453 // Split triangular piece into two diagonals. | |
454 RECT& left_half = bottom_section; | |
455 RECT right_half = bottom_section; | |
456 right_half.left += ((bottom_section.right - bottom_section.left) / 2); | |
457 left_half.right = right_half.left; | |
458 DrawEdge(hdc, &left_half, EDGE_RAISED, | |
459 BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); | |
460 DrawEdge(hdc, &right_half, EDGE_RAISED, | |
461 BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); | |
462 | |
463 // If the button is pressed, draw hatching. | |
464 if (classic_state & DFCS_PUSHED) { | |
465 SkPaint paint; | |
466 SetCheckerboardShader(&paint, *rect); | |
467 | |
468 // Fill all three pieces with the pattern. | |
469 canvas->drawIRect(skia::RECTToSkIRect(top_section), paint); | |
470 | |
471 SkScalar left_triangle_top = SkIntToScalar(left_half.top); | |
472 SkScalar left_triangle_right = SkIntToScalar(left_half.right); | |
473 SkPath left_triangle; | |
474 left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top); | |
475 left_triangle.lineTo(left_triangle_right, left_triangle_top); | |
476 left_triangle.lineTo(left_triangle_right, | |
477 SkIntToScalar(left_half.bottom)); | |
478 left_triangle.close(); | |
479 canvas->drawPath(left_triangle, paint); | |
480 | |
481 SkScalar right_triangle_left = SkIntToScalar(right_half.left); | |
482 SkScalar right_triangle_top = SkIntToScalar(right_half.top); | |
483 SkPath right_triangle; | |
484 right_triangle.moveTo(right_triangle_left, right_triangle_top); | |
485 right_triangle.lineTo(SkIntToScalar(right_half.right), | |
486 right_triangle_top); | |
487 right_triangle.lineTo(right_triangle_left, | |
488 SkIntToScalar(right_half.bottom)); | |
489 right_triangle.close(); | |
490 canvas->drawPath(right_triangle, paint); | |
491 } | |
492 } | |
493 return S_OK; | |
494 } | |
495 | |
496 // <-a-> | |
497 // [ ***** ] | |
498 // ____ | | | |
499 // <-a-> <------b-----> | |
500 // a: object_width | |
501 // b: frame_width | |
502 // *: animating object | |
503 // | |
504 // - the animation goes from "[" to "]" repeatedly. | |
505 // - the animation offset is at first "|" | |
506 // | |
507 static int ComputeAnimationProgress(int frame_width, | |
508 int object_width, | |
509 int pixels_per_second, | |
510 double animated_seconds) { | |
511 int animation_width = frame_width + object_width; | |
512 double interval = static_cast<double>(animation_width) / pixels_per_second; | |
513 double ratio = fmod(animated_seconds, interval) / interval; | |
514 return static_cast<int>(animation_width * ratio) - object_width; | |
515 } | |
516 | |
517 static RECT InsetRect(const RECT* rect, int size) { | |
518 gfx::Rect result(*rect); | |
519 result.Inset(size, size); | |
520 return result.ToRECT(); | |
521 } | |
522 | |
523 HRESULT NativeTheme::PaintProgressBar(HDC hdc, | |
524 RECT* bar_rect, | |
525 RECT* value_rect, | |
526 bool determinate, | |
527 double animated_seconds, | |
528 skia::PlatformCanvas* canvas) const { | |
529 // There is no documentation about the animation speed, frame-rate, nor | |
530 // size of moving overlay of the indeterminate progress bar. | |
531 // So we just observed real-world programs and guessed following parameters. | |
532 const int kDeteminateOverlayPixelsPerSecond = 300; | |
533 const int kDeteminateOverlayWidth = 120; | |
534 const int kIndeterminateOverlayPixelsPerSecond = 175; | |
535 const int kVistaIndeterminateOverlayWidth = 120; | |
536 const int kXPIndeterminateOverlayWidth = 55; | |
537 // The thickness of the bar frame inside |value_rect| | |
538 const int kXPBarPadding = 3; | |
539 | |
540 bool pre_vista = base::win::GetVersion() < base::win::VERSION_VISTA; | |
541 HANDLE handle = GetThemeHandle(PROGRESS); | |
542 if (handle && draw_theme_ && draw_theme_ex_) { | |
543 draw_theme_(handle, hdc, PP_BAR, 0, bar_rect, NULL); | |
544 | |
545 int bar_width = bar_rect->right - bar_rect->left; | |
546 if (determinate) { | |
547 // TODO(morrita): this RTL guess can be wrong. | |
548 // We should pass the direction from WebKit side. | |
549 bool is_rtl = (bar_rect->right == value_rect->right && | |
550 bar_rect->left != value_rect->left); | |
551 // We should care the direction here because PP_CNUNK painting | |
552 // is asymmetric. | |
553 DTBGOPTS value_draw_options; | |
554 value_draw_options.dwSize = sizeof(DTBGOPTS); | |
555 value_draw_options.dwFlags = is_rtl ? DTBG_MIRRORDC : 0; | |
556 value_draw_options.rcClip = *bar_rect; | |
557 | |
558 if (pre_vista) { | |
559 // On XP, progress bar is chunk-style and has no glossy effect. | |
560 // We need to shrink destination rect to fit the part inside the bar | |
561 // with an appropriate margin. | |
562 RECT shrunk_value_rect = InsetRect(value_rect, kXPBarPadding); | |
563 draw_theme_ex_(handle, hdc, PP_CHUNK, 0, | |
564 &shrunk_value_rect, &value_draw_options); | |
565 } else { | |
566 // On Vista or later, the progress bar part has a | |
567 // single-block value part. It also has glossy effect. | |
568 // And the value part has exactly same height as the bar part | |
569 // so we don't need to shrink the rect. | |
570 draw_theme_ex_(handle, hdc, PP_FILL, 0, | |
571 value_rect, &value_draw_options); | |
572 | |
573 int dx = ComputeAnimationProgress(bar_width, | |
574 kDeteminateOverlayWidth, | |
575 kDeteminateOverlayPixelsPerSecond, | |
576 animated_seconds); | |
577 RECT overlay_rect = *value_rect; | |
578 overlay_rect.left += dx; | |
579 overlay_rect.right = overlay_rect.left + kDeteminateOverlayWidth; | |
580 draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, value_rect); | |
581 } | |
582 } else { | |
583 // A glossy overlay for indeterminate progress bar has small pause | |
584 // after each animation. We emulate this by adding an invisible margin | |
585 // the animation has to traverse. | |
586 int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond; | |
587 int overlay_width = pre_vista ? | |
588 kXPIndeterminateOverlayWidth : kVistaIndeterminateOverlayWidth; | |
589 int dx = ComputeAnimationProgress(width_with_margin, | |
590 overlay_width, | |
591 kIndeterminateOverlayPixelsPerSecond, | |
592 animated_seconds); | |
593 RECT overlay_rect = *bar_rect; | |
594 overlay_rect.left += dx; | |
595 overlay_rect.right = overlay_rect.left + overlay_width; | |
596 if (pre_vista) { | |
597 RECT shrunk_rect = InsetRect(&overlay_rect, kXPBarPadding); | |
598 RECT shrunk_bar_rect = InsetRect(bar_rect, kXPBarPadding); | |
599 draw_theme_(handle, hdc, PP_CHUNK, 0, &shrunk_rect, &shrunk_bar_rect); | |
600 } else { | |
601 draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, bar_rect); | |
602 } | |
603 } | |
604 | |
605 return S_OK; | |
606 } | |
607 | |
608 HBRUSH bg_brush = GetSysColorBrush(COLOR_BTNFACE); | |
609 HBRUSH fg_brush = GetSysColorBrush(COLOR_BTNSHADOW); | |
610 FillRect(hdc, bar_rect, bg_brush); | |
611 FillRect(hdc, value_rect, fg_brush); | |
612 DrawEdge(hdc, bar_rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); | |
613 return S_OK; | |
614 } | |
615 | |
616 HRESULT NativeTheme::PaintTextField(HDC hdc, | |
617 int part_id, | |
618 int state_id, | |
619 int classic_state, | |
620 RECT* rect, | |
621 COLORREF color, | |
622 bool fill_content_area, | |
623 bool draw_edges) const { | |
624 // TODO(ojan): http://b/1210017 Figure out how to give the ability to | |
625 // exclude individual edges from being drawn. | |
626 | |
627 HANDLE handle = GetThemeHandle(TEXTFIELD); | |
628 // TODO(mpcomplete): can we detect if the color is specified by the user, | |
629 // and if not, just use the system color? | |
630 // CreateSolidBrush() accepts a RGB value but alpha must be 0. | |
631 HBRUSH bg_brush = CreateSolidBrush(color); | |
632 HRESULT hr; | |
633 // DrawThemeBackgroundEx was introduced in XP SP2, so that it's possible | |
634 // draw_theme_ex_ is NULL and draw_theme_ is non-null. | |
635 if (handle && (draw_theme_ex_ || (draw_theme_ && draw_edges))) { | |
636 if (draw_theme_ex_) { | |
637 static DTBGOPTS omit_border_options = { | |
638 sizeof(DTBGOPTS), | |
639 DTBG_OMITBORDER, | |
640 {0,0,0,0} | |
641 }; | |
642 DTBGOPTS* draw_opts = draw_edges ? NULL : &omit_border_options; | |
643 hr = draw_theme_ex_(handle, hdc, part_id, state_id, rect, draw_opts); | |
644 } else { | |
645 hr = draw_theme_(handle, hdc, part_id, state_id, rect, NULL); | |
646 } | |
647 | |
648 // TODO(maruel): Need to be fixed if get_theme_content_rect_ is NULL. | |
649 if (fill_content_area && get_theme_content_rect_) { | |
650 RECT content_rect; | |
651 hr = get_theme_content_rect_(handle, hdc, part_id, state_id, rect, | |
652 &content_rect); | |
653 FillRect(hdc, &content_rect, bg_brush); | |
654 } | |
655 } else { | |
656 // Draw it manually. | |
657 if (draw_edges) | |
658 DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); | |
659 | |
660 if (fill_content_area) { | |
661 FillRect(hdc, rect, (classic_state & DFCS_INACTIVE) ? | |
662 reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1) : bg_brush); | |
663 } | |
664 hr = S_OK; | |
665 } | |
666 DeleteObject(bg_brush); | |
667 return hr; | |
668 } | |
669 | |
670 bool NativeTheme::IsThemingActive() const { | |
671 if (is_theme_active_) | |
672 return !!is_theme_active_(); | |
673 return false; | |
674 } | |
675 | |
676 HRESULT NativeTheme::GetThemePartSize(ThemeName theme_name, | |
677 HDC hdc, | |
678 int part_id, | |
679 int state_id, | |
680 RECT* rect, | |
681 int ts, | |
682 SIZE* size) const { | |
683 HANDLE handle = GetThemeHandle(theme_name); | |
684 if (handle && get_theme_part_size_) | |
685 return get_theme_part_size_(handle, hdc, part_id, state_id, rect, ts, size); | |
686 | |
687 return E_NOTIMPL; | |
688 } | |
689 | |
690 HRESULT NativeTheme::GetThemeColor(ThemeName theme, | |
691 int part_id, | |
692 int state_id, | |
693 int prop_id, | |
694 SkColor* color) const { | |
695 HANDLE handle = GetThemeHandle(theme); | |
696 if (handle && get_theme_color_) { | |
697 COLORREF color_ref; | |
698 if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) == | |
699 S_OK) { | |
700 *color = skia::COLORREFToSkColor(color_ref); | |
701 return S_OK; | |
702 } | |
703 } | |
704 return E_NOTIMPL; | |
705 } | |
706 | |
707 SkColor NativeTheme::GetThemeColorWithDefault(ThemeName theme, | |
708 int part_id, | |
709 int state_id, | |
710 int prop_id, | |
711 int default_sys_color) const { | |
712 SkColor color; | |
713 if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK) | |
714 color = skia::COLORREFToSkColor(GetSysColor(default_sys_color)); | |
715 return color; | |
716 } | |
717 | |
718 HRESULT NativeTheme::GetThemeInt(ThemeName theme, | |
719 int part_id, | |
720 int state_id, | |
721 int prop_id, | |
722 int *value) const { | |
723 HANDLE handle = GetThemeHandle(theme); | |
724 if (handle && get_theme_int_) | |
725 return get_theme_int_(handle, part_id, state_id, prop_id, value); | |
726 return E_NOTIMPL; | |
727 } | |
728 | |
729 Size NativeTheme::GetThemeBorderSize(ThemeName theme) const { | |
730 // For simplicity use the wildcard state==0, part==0, since it works | |
731 // for the cases we currently depend on. | |
732 int border; | |
733 if (GetThemeInt(theme, 0, 0, TMT_BORDERSIZE, &border) == S_OK) | |
734 return Size(border, border); | |
735 else | |
736 return Size(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)); | |
737 } | |
738 | |
739 | |
740 void NativeTheme::DisableTheming() const { | |
741 if (!set_theme_properties_) | |
742 return; | |
743 set_theme_properties_(0); | |
744 } | |
745 | |
746 HRESULT NativeTheme::PaintFrameControl(HDC hdc, | |
747 RECT* rect, | |
748 UINT type, | |
749 UINT state, | |
750 ControlState control_state) const { | |
751 const int width = rect->right - rect->left; | |
752 const int height = rect->bottom - rect->top; | |
753 | |
754 // DrawFrameControl for menu arrow/check wants a monochrome bitmap. | |
755 base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL)); | |
756 | |
757 if (mask_bitmap == NULL) | |
758 return E_OUTOFMEMORY; | |
759 | |
760 base::win::ScopedHDC bitmap_dc(CreateCompatibleDC(NULL)); | |
761 HGDIOBJ org_bitmap = SelectObject(bitmap_dc, mask_bitmap); | |
762 RECT local_rect = { 0, 0, width, height }; | |
763 DrawFrameControl(bitmap_dc, &local_rect, type, state); | |
764 | |
765 // We're going to use BitBlt with a b&w mask. This results in using the dest | |
766 // dc's text color for the black bits in the mask, and the dest dc's | |
767 // background color for the white bits in the mask. DrawFrameControl draws the | |
768 // check in black, and the background in white. | |
769 int bg_color_key; | |
770 int text_color_key; | |
771 switch (control_state) { | |
772 case CONTROL_HIGHLIGHTED: | |
773 bg_color_key = COLOR_HIGHLIGHT; | |
774 text_color_key = COLOR_HIGHLIGHTTEXT; | |
775 break; | |
776 case CONTROL_NORMAL: | |
777 bg_color_key = COLOR_MENU; | |
778 text_color_key = COLOR_MENUTEXT; | |
779 break; | |
780 case CONTROL_DISABLED: | |
781 bg_color_key = COLOR_MENU; | |
782 text_color_key = COLOR_GRAYTEXT; | |
783 break; | |
784 default: | |
785 NOTREACHED(); | |
786 bg_color_key = COLOR_MENU; | |
787 text_color_key = COLOR_MENUTEXT; | |
788 break; | |
789 } | |
790 COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key)); | |
791 COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key)); | |
792 BitBlt(hdc, rect->left, rect->top, width, height, bitmap_dc, 0, 0, SRCCOPY); | |
793 SetBkColor(hdc, old_bg_color); | |
794 SetTextColor(hdc, old_text_color); | |
795 | |
796 SelectObject(bitmap_dc, org_bitmap); | |
797 | |
798 return S_OK; | |
799 } | |
800 | |
801 void NativeTheme::CloseHandles() const | |
802 { | |
803 if (!close_theme_) | |
804 return; | |
805 | |
806 for (int i = 0; i < LAST; ++i) { | |
807 if (theme_handles_[i]) | |
808 close_theme_(theme_handles_[i]); | |
809 theme_handles_[i] = NULL; | |
810 } | |
811 } | |
812 | |
813 bool NativeTheme::IsClassicTheme(ThemeName name) const { | |
814 if (!theme_dll_) | |
815 return true; | |
816 | |
817 return !GetThemeHandle(name); | |
818 } | |
819 | |
820 HANDLE NativeTheme::GetThemeHandle(ThemeName theme_name) const | |
821 { | |
822 if (!open_theme_ || theme_name < 0 || theme_name >= LAST) | |
823 return 0; | |
824 | |
825 if (theme_handles_[theme_name]) | |
826 return theme_handles_[theme_name]; | |
827 | |
828 // Not found, try to load it. | |
829 HANDLE handle = 0; | |
830 switch (theme_name) { | |
831 case BUTTON: | |
832 handle = open_theme_(NULL, L"Button"); | |
833 break; | |
834 case LIST: | |
835 handle = open_theme_(NULL, L"Listview"); | |
836 break; | |
837 case MENU: | |
838 handle = open_theme_(NULL, L"Menu"); | |
839 break; | |
840 case MENULIST: | |
841 handle = open_theme_(NULL, L"Combobox"); | |
842 break; | |
843 case SCROLLBAR: | |
844 handle = open_theme_(NULL, L"Scrollbar"); | |
845 break; | |
846 case STATUS: | |
847 handle = open_theme_(NULL, L"Status"); | |
848 break; | |
849 case TAB: | |
850 handle = open_theme_(NULL, L"Tab"); | |
851 break; | |
852 case TEXTFIELD: | |
853 handle = open_theme_(NULL, L"Edit"); | |
854 break; | |
855 case TRACKBAR: | |
856 handle = open_theme_(NULL, L"Trackbar"); | |
857 break; | |
858 case WINDOW: | |
859 handle = open_theme_(NULL, L"Window"); | |
860 break; | |
861 case PROGRESS: | |
862 handle = open_theme_(NULL, L"Progress"); | |
863 break; | |
864 case SPIN: | |
865 handle = open_theme_(NULL, L"Spin"); | |
866 break; | |
867 default: | |
868 NOTREACHED(); | |
869 } | |
870 theme_handles_[theme_name] = handle; | |
871 return handle; | |
872 } | |
873 | |
874 } // namespace gfx | |
OLD | NEW |