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 "chrome/browser/ui/libgtkui/gtk_util.h" | 5 #include "chrome/browser/ui/libgtkui/gtk_util.h" |
6 | 6 |
7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
8 #include <gdk/gdk.h> | 8 #include <gdk/gdk.h> |
9 #include <gdk/gdkx.h> | 9 #include <gdk/gdkx.h> |
10 #include <gtk/gtk.h> | 10 #include <gtk/gtk.h> |
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
213 CAIRO_FORMAT_ARGB32, | 213 CAIRO_FORMAT_ARGB32, |
214 bitmap.width(), | 214 bitmap.width(), |
215 bitmap.height(), | 215 bitmap.height(), |
216 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, bitmap.width()))), | 216 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, bitmap.width()))), |
217 cairo_(cairo_create(surface_)) {} | 217 cairo_(cairo_create(surface_)) {} |
218 | 218 |
219 CairoSurface::CairoSurface(const gfx::Size& size) | 219 CairoSurface::CairoSurface(const gfx::Size& size) |
220 : surface_(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, | 220 : surface_(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, |
221 size.width(), | 221 size.width(), |
222 size.height())), | 222 size.height())), |
223 cairo_(cairo_create(surface_)) {} | 223 cairo_(cairo_create(surface_)) { |
| 224 DCHECK(cairo_surface_status(surface_) == CAIRO_STATUS_SUCCESS); |
| 225 // Clear the surface. |
| 226 cairo_save(cairo_); |
| 227 cairo_set_source_rgba(cairo_, 0, 0, 0, 0); |
| 228 cairo_set_operator(cairo_, CAIRO_OPERATOR_SOURCE); |
| 229 cairo_paint(cairo_); |
| 230 cairo_restore(cairo_); |
| 231 } |
224 | 232 |
225 CairoSurface::~CairoSurface() { | 233 CairoSurface::~CairoSurface() { |
226 cairo_destroy(cairo_); | 234 cairo_destroy(cairo_); |
227 cairo_surface_destroy(surface_); | 235 cairo_surface_destroy(surface_); |
228 } | 236 } |
229 | 237 |
230 SkColor CairoSurface::GetAveragePixelValue(bool only_frame_pixels) { | 238 SkColor CairoSurface::GetAveragePixelValue(bool frame) { |
231 cairo_surface_flush(surface_); | 239 cairo_surface_flush(surface_); |
232 int num_samples = 0; | |
233 long a = 0, r = 0, g = 0, b = 0; | |
234 SkColor* data = | 240 SkColor* data = |
235 reinterpret_cast<SkColor*>(cairo_image_surface_get_data(surface_)); | 241 reinterpret_cast<SkColor*>(cairo_image_surface_get_data(surface_)); |
236 int width = cairo_image_surface_get_width(surface_); | 242 int width = cairo_image_surface_get_width(surface_); |
237 int height = cairo_image_surface_get_height(surface_); | 243 int height = cairo_image_surface_get_height(surface_); |
238 DCHECK(4 * width == cairo_image_surface_get_stride(surface_)); | 244 DCHECK(4 * width == cairo_image_surface_get_stride(surface_)); |
239 auto accumulate = [&](int x, int y) mutable { | 245 long a = 0, r = 0, g = 0, b = 0; |
240 SkColor color = data[y * width + x]; | 246 unsigned int max_alpha = 0; |
241 int alpha = SkColorGetA(color); | 247 for (int i = 0; i < width * height; i++) { |
242 a += alpha; | 248 SkColor color = data[i]; |
243 r += alpha * SkColorGetR(color); | 249 max_alpha = std::max(SkColorGetA(color), max_alpha); |
244 g += alpha * SkColorGetG(color); | 250 a += SkColorGetA(color); |
245 b += alpha * SkColorGetB(color); | 251 r += SkColorGetR(color); |
246 num_samples++; | 252 g += SkColorGetG(color); |
247 }; | 253 b += SkColorGetB(color); |
248 if (width == 1 || height == 1 || !only_frame_pixels) { | |
249 // Count every pixel in the surface. | |
250 for (int x = 0; x < width; x++) | |
251 for (int y = 0; y < height; y++) | |
252 accumulate(x, y); | |
253 } else { | |
254 // Count the pixels in the top and bottom rows. | |
255 for (int x = 0; x < width; x++) | |
256 for (int y : {0, height - 1}) | |
257 accumulate(x, y); | |
258 // Count the pixels in the left and right columns. | |
259 for (int x : {0, width - 1}) | |
260 for (int y = 1; y < height - 1; y++) | |
261 accumulate(x, y); | |
262 } | 254 } |
263 if (a == 0) | 255 if (a == 0) |
264 return SK_ColorTRANSPARENT; | 256 return SK_ColorTRANSPARENT; |
265 return SkColorSetARGB(a / num_samples, r / a, g / a, b / a); | 257 return SkColorSetARGB(frame ? max_alpha : a / (width * height), r * 255 / a, |
| 258 g * 255 / a, b * 255 / a); |
266 } | 259 } |
267 | 260 |
268 bool GtkVersionCheck(int major, int minor, int micro) { | 261 bool GtkVersionCheck(int major, int minor, int micro) { |
269 static int actual_major = gtk_get_major_version(); | 262 static int actual_major = gtk_get_major_version(); |
270 if (actual_major > major) | 263 if (actual_major > major) |
271 return true; | 264 return true; |
272 else if (actual_major < major) | 265 else if (actual_major < major) |
273 return false; | 266 return false; |
274 | 267 |
275 static int actual_minor = gtk_get_minor_version(); | 268 static int actual_minor = gtk_get_minor_version(); |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
329 case '.': | 322 case '.': |
330 part_type = CSS_CLASS; | 323 part_type = CSS_CLASS; |
331 break; | 324 break; |
332 case ':': | 325 case ':': |
333 part_type = CSS_PSEUDOCLASS; | 326 part_type = CSS_PSEUDOCLASS; |
334 break; | 327 break; |
335 default: | 328 default: |
336 NOTREACHED(); | 329 NOTREACHED(); |
337 } | 330 } |
338 } else { | 331 } else { |
| 332 static auto* _gtk_widget_path_iter_set_object_name = |
| 333 reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(dlsym( |
| 334 GetGtk3SharedLibrary(), "gtk_widget_path_iter_set_object_name")); |
339 switch (part_type) { | 335 switch (part_type) { |
340 case CSS_NAME: { | 336 case CSS_NAME: { |
341 if (GtkVersionCheck(3, 20)) { | 337 if (GtkVersionCheck(3, 20)) { |
342 static auto* _gtk_widget_path_iter_set_object_name = | |
343 reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>( | |
344 dlsym(GetGtk3SharedLibrary(), | |
345 "gtk_widget_path_iter_set_object_name")); | |
346 DCHECK(_gtk_widget_path_iter_set_object_name); | |
347 _gtk_widget_path_iter_set_object_name(path, -1, t.token().c_str()); | 338 _gtk_widget_path_iter_set_object_name(path, -1, t.token().c_str()); |
348 } else { | 339 } else { |
349 gtk_widget_path_iter_add_class(path, -1, t.token().c_str()); | 340 gtk_widget_path_iter_add_class(path, -1, t.token().c_str()); |
350 } | 341 } |
351 break; | 342 break; |
352 } | 343 } |
353 case CSS_TYPE: { | 344 case CSS_TYPE: { |
354 GType type = g_type_from_name(t.token().c_str()); | 345 GType type = g_type_from_name(t.token().c_str()); |
355 DCHECK(type); | 346 DCHECK(type); |
356 gtk_widget_path_append_type(path, type); | 347 gtk_widget_path_append_type(path, type); |
| 348 if (GtkVersionCheck(3, 20)) { |
| 349 if (t.token() == "GtkLabel") |
| 350 _gtk_widget_path_iter_set_object_name(path, -1, "label"); |
| 351 } |
357 break; | 352 break; |
358 } | 353 } |
359 case CSS_CLASS: { | 354 case CSS_CLASS: { |
360 gtk_widget_path_iter_add_class(path, -1, t.token().c_str()); | 355 gtk_widget_path_iter_add_class(path, -1, t.token().c_str()); |
361 break; | 356 break; |
362 } | 357 } |
363 case CSS_PSEUDOCLASS: { | 358 case CSS_PSEUDOCLASS: { |
364 GtkStateFlags state_flag = GTK_STATE_FLAG_NORMAL; | 359 GtkStateFlags state_flag = GTK_STATE_FLAG_NORMAL; |
365 for (const auto& pseudo_class_entry : pseudo_classes) { | 360 for (const auto& pseudo_class_entry : pseudo_classes) { |
366 if (strcmp(pseudo_class_entry.name, t.token().c_str()) == 0) { | 361 if (strcmp(pseudo_class_entry.name, t.token().c_str()) == 0) { |
367 state_flag = pseudo_class_entry.state_flag; | 362 state_flag = pseudo_class_entry.state_flag; |
368 break; | 363 break; |
369 } | 364 } |
370 } | 365 } |
371 state = static_cast<GtkStateFlags>(state | state_flag); | 366 state = static_cast<GtkStateFlags>(state | state_flag); |
372 break; | 367 break; |
373 } | 368 } |
374 } | 369 } |
375 } | 370 } |
376 } | 371 } |
377 | 372 |
378 // Always add a "chromium" class so that themes can style chromium | 373 // Always add a "chromium" class so that themes can style chromium |
379 // widgets specially if they want to. | 374 // widgets specially if they want to. |
380 gtk_widget_path_iter_add_class(path, -1, "chromium"); | 375 gtk_widget_path_iter_add_class(path, -1, "chromium"); |
381 | 376 |
382 auto child_context = ScopedStyleContext(gtk_style_context_new()); | 377 ScopedStyleContext child_context(gtk_style_context_new()); |
383 gtk_style_context_set_path(child_context, path); | 378 gtk_style_context_set_path(child_context, path); |
384 gtk_style_context_set_state(child_context, state); | 379 gtk_style_context_set_state(child_context, state); |
385 gtk_style_context_set_parent(child_context, context); | 380 gtk_style_context_set_parent(child_context, context); |
386 gtk_widget_path_unref(path); | 381 gtk_widget_path_unref(path); |
387 return child_context; | 382 return child_context; |
388 } | 383 } |
389 | 384 |
390 ScopedStyleContext GetStyleContextFromCss(const char* css_selector) { | 385 ScopedStyleContext GetStyleContextFromCss(const char* css_selector) { |
391 // Prepend "GtkWindow.background" to the selector since all widgets must live | 386 // Prepend a window node to the selector since all widgets must live |
392 // in a window, but we don't want to specify that every time. | 387 // in a window, but we don't want to specify that every time. |
393 auto context = AppendCssNodeToStyleContext(nullptr, "GtkWindow.background"); | 388 auto context = |
| 389 AppendCssNodeToStyleContext(nullptr, "GtkWindow#window.background"); |
394 | 390 |
395 for (const auto& widget_type : | 391 for (const auto& widget_type : |
396 base::SplitString(css_selector, base::kWhitespaceASCII, | 392 base::SplitString(css_selector, base::kWhitespaceASCII, |
397 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { | 393 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { |
398 context = AppendCssNodeToStyleContext(context, widget_type); | 394 context = AppendCssNodeToStyleContext(context, widget_type); |
399 } | 395 } |
400 return context; | 396 return context; |
401 } | 397 } |
402 | 398 |
403 SkColor GdkRgbaToSkColor(const GdkRGBA& color) { | 399 SkColor GdkRgbaToSkColor(const GdkRGBA& color) { |
404 return SkColorSetARGB(color.alpha * 255, color.red * 255, color.green * 255, | 400 return SkColorSetARGB(color.alpha * 255, color.red * 255, color.green * 255, |
405 color.blue * 255); | 401 color.blue * 255); |
406 } | 402 } |
407 | 403 |
408 SkColor SkColorFromStyleContext(GtkStyleContext* context) { | 404 SkColor GetFgColorFromStyleContext(GtkStyleContext* context) { |
409 GdkRGBA color; | 405 GdkRGBA color; |
410 gtk_style_context_get_color(context, gtk_style_context_get_state(context), | 406 gtk_style_context_get_color(context, gtk_style_context_get_state(context), |
411 &color); | 407 &color); |
412 return GdkRgbaToSkColor(color); | 408 return GdkRgbaToSkColor(color); |
413 } | 409 } |
414 | 410 |
415 SkColor GetFgColor(const char* css_selector) { | 411 SkColor GetBgColorFromStyleContext(GtkStyleContext* context) { |
416 return SkColorFromStyleContext(GetStyleContextFromCss(css_selector)); | 412 // Backgrounds are more general than solid colors (eg. gradients), |
| 413 // but chromium requires us to boil this down to one color. We |
| 414 // cannot use the background-color here because some themes leave it |
| 415 // set to a garbage color because a background-image will cover it |
| 416 // anyway. So we instead render the background into a 24x24 bitmap, |
| 417 // removing any borders, and hope that we get a good color. |
| 418 ApplyCssToContext(context, |
| 419 "* {" |
| 420 "border-radius: 0px;" |
| 421 "border-style: none;" |
| 422 "box-shadow: none;" |
| 423 "}"); |
| 424 gfx::Size size(24, 24); |
| 425 CairoSurface surface(size); |
| 426 RenderBackground(size, surface.cairo(), context); |
| 427 return surface.GetAveragePixelValue(false); |
417 } | 428 } |
418 | 429 |
419 GtkCssProvider* GetCssProvider(const char* css) { | 430 SkColor GetFgColor(const char* css_selector) { |
| 431 return GetFgColorFromStyleContext(GetStyleContextFromCss(css_selector)); |
| 432 } |
| 433 |
| 434 ScopedCssProvider GetCssProvider(const char* css) { |
420 GtkCssProvider* provider = gtk_css_provider_new(); | 435 GtkCssProvider* provider = gtk_css_provider_new(); |
421 GError* error = nullptr; | 436 GError* error = nullptr; |
422 gtk_css_provider_load_from_data(provider, css, -1, &error); | 437 gtk_css_provider_load_from_data(provider, css, -1, &error); |
423 DCHECK(!error); | 438 DCHECK(!error); |
424 return provider; | 439 return ScopedCssProvider(provider); |
425 } | 440 } |
426 | 441 |
427 void ApplyCssToContext(GtkStyleContext* context, GtkCssProvider* provider) { | 442 void ApplyCssProviderToContext(GtkStyleContext* context, |
| 443 GtkCssProvider* provider) { |
428 while (context) { | 444 while (context) { |
429 gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), | 445 gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), |
430 G_MAXUINT); | 446 G_MAXUINT); |
431 context = gtk_style_context_get_parent(context); | 447 context = gtk_style_context_get_parent(context); |
432 } | 448 } |
433 } | 449 } |
434 | 450 |
435 void RemoveBorders(GtkStyleContext* context) { | 451 void ApplyCssToContext(GtkStyleContext* context, const char* css) { |
436 static GtkCssProvider* provider = GetCssProvider( | 452 auto provider = GetCssProvider(css); |
437 "* {" | 453 ApplyCssProviderToContext(context, provider); |
438 "border-style: none;" | |
439 "border-radius: 0px;" | |
440 "border-width: 0px;" | |
441 "border-image-width: 0px;" | |
442 "box-shadow: none;" | |
443 "padding: 0px;" | |
444 "margin: 0px;" | |
445 "outline: none;" | |
446 "outline-width: 0px;" | |
447 "}"); | |
448 ApplyCssToContext(context, provider); | |
449 } | |
450 | |
451 void AddBorders(GtkStyleContext* context) { | |
452 static GtkCssProvider* provider = GetCssProvider( | |
453 "* {" | |
454 "border-style: solid;" | |
455 "border-radius: 0px;" | |
456 "border-width: 1px;" | |
457 "box-shadow: none;" | |
458 "padding: 0px;" | |
459 "margin: 0px;" | |
460 "outline: none;" | |
461 "outline-width: 0px;" | |
462 "}"); | |
463 ApplyCssToContext(context, provider); | |
464 } | 454 } |
465 | 455 |
466 void RenderBackground(const gfx::Size& size, | 456 void RenderBackground(const gfx::Size& size, |
467 cairo_t* cr, | 457 cairo_t* cr, |
468 GtkStyleContext* context) { | 458 GtkStyleContext* context) { |
469 if (!context) | 459 if (!context) |
470 return; | 460 return; |
471 RenderBackground(size, cr, gtk_style_context_get_parent(context)); | 461 RenderBackground(size, cr, gtk_style_context_get_parent(context)); |
472 gtk_render_background(context, cr, 0, 0, size.width(), size.height()); | 462 gtk_render_background(context, cr, 0, 0, size.width(), size.height()); |
473 } | 463 } |
474 | 464 |
475 gfx::Size GetMinimumWidgetSize(GtkStyleContext* context) { | |
476 if (!GtkVersionCheck(3, 20)) | |
477 return gfx::Size(1, 1); | |
478 int w = 1, h = 1; | |
479 gtk_style_context_get(context, gtk_style_context_get_state(context), | |
480 "min-width", &w, "min-height", &h, NULL); | |
481 DCHECK(w >= 0 && h >= 0); | |
482 return gfx::Size(w ? w : 1, h ? h : 1); | |
483 } | |
484 | |
485 SkColor GetBgColor(const char* css_selector) { | 465 SkColor GetBgColor(const char* css_selector) { |
486 // Backgrounds are more general than solid colors (eg. gradients), | 466 return GetBgColorFromStyleContext(GetStyleContextFromCss(css_selector)); |
487 // but chromium requires us to boil this down to one color. We | |
488 // cannot use the background-color here because some themes leave it | |
489 // set to a garbage color because a background-image will cover it | |
490 // anyway. So we instead render the background into a single pixel, | |
491 // removing any borders, and hope that we get a good color. | |
492 auto context = GetStyleContextFromCss(css_selector); | |
493 RemoveBorders(context); | |
494 gfx::Size size = GetMinimumWidgetSize(context); | |
495 CairoSurface surface(size); | |
496 RenderBackground(size, surface.cairo(), context); | |
497 return surface.GetAveragePixelValue(false); | |
498 } | 467 } |
499 | 468 |
500 SkColor GetBorderColor(const char* css_selector) { | 469 SkColor GetBorderColor(const char* css_selector) { |
501 // Borders have the same issue as backgrounds, due to the | 470 // Borders have the same issue as backgrounds, due to the |
502 // border-image property. | 471 // border-image property. |
503 auto context = GetStyleContextFromCss(css_selector); | 472 auto context = GetStyleContextFromCss(css_selector); |
504 GtkStateFlags state = gtk_style_context_get_state(context); | 473 gfx::Size size(24, 24); |
505 GtkBorderStyle border_style = GTK_BORDER_STYLE_NONE; | |
506 gtk_style_context_get(context, state, GTK_STYLE_PROPERTY_BORDER_STYLE, | |
507 &border_style, nullptr); | |
508 GtkBorder border; | |
509 gtk_style_context_get_border(context, state, &border); | |
510 if ((border_style == GTK_BORDER_STYLE_NONE || | |
511 border_style == GTK_BORDER_STYLE_HIDDEN) || | |
512 (!border.left && !border.right && !border.top && !border.bottom)) { | |
513 return SK_ColorTRANSPARENT; | |
514 } | |
515 | |
516 AddBorders(context); | |
517 gfx::Size size = GetMinimumWidgetSize(context); | |
518 CairoSurface surface(size); | 474 CairoSurface surface(size); |
519 RenderBackground(size, surface.cairo(), context); | |
520 gtk_render_frame(context, surface.cairo(), 0, 0, size.width(), size.height()); | 475 gtk_render_frame(context, surface.cairo(), 0, 0, size.width(), size.height()); |
521 return surface.GetAveragePixelValue(true); | 476 return surface.GetAveragePixelValue(true); |
522 } | 477 } |
523 | 478 |
| 479 ScopedStyleContext GetSelectedStyleContext(const char* css_selector) { |
| 480 auto context = GetStyleContextFromCss(css_selector); |
| 481 if (GtkVersionCheck(3, 20)) { |
| 482 context = AppendCssNodeToStyleContext(context, "#selection"); |
| 483 } else { |
| 484 GtkStateFlags state = gtk_style_context_get_state(context); |
| 485 state = static_cast<GtkStateFlags>(state | GTK_STATE_FLAG_SELECTED); |
| 486 gtk_style_context_set_state(context, state); |
| 487 } |
| 488 return context; |
| 489 } |
| 490 |
| 491 SkColor GetSelectedTextColor(const char* css_selector) { |
| 492 return GetFgColorFromStyleContext(GetSelectedStyleContext(css_selector)); |
| 493 } |
| 494 |
| 495 SkColor GetSelectedBgColor(const char* css_selector) { |
| 496 auto context = GetSelectedStyleContext(css_selector); |
| 497 if (GtkVersionCheck(3, 20)) |
| 498 return GetBgColorFromStyleContext(context); |
| 499 // This is verbatim how Gtk gets the selection color on versions before 3.20. |
| 500 GdkRGBA selection_color; |
| 501 G_GNUC_BEGIN_IGNORE_DEPRECATIONS; |
| 502 gtk_style_context_get_background_color( |
| 503 context, gtk_style_context_get_state(context), &selection_color); |
| 504 G_GNUC_END_IGNORE_DEPRECATIONS; |
| 505 return GdkRgbaToSkColor(selection_color); |
| 506 } |
| 507 |
524 SkColor GetSeparatorColor(const char* css_selector) { | 508 SkColor GetSeparatorColor(const char* css_selector) { |
525 if (!GtkVersionCheck(3, 20)) | 509 if (!GtkVersionCheck(3, 20)) |
526 return GetFgColor(css_selector); | 510 return GetFgColor(css_selector); |
527 | 511 |
528 // Some themes use borders to render separators, others use a | 512 auto context = GetStyleContextFromCss(css_selector); |
529 // background color. Only return the border color if there is one. | 513 int w = 1, h = 1; |
530 SkColor border = GetBorderColor(css_selector); | 514 gtk_style_context_get(context, gtk_style_context_get_state(context), |
531 if (SkColorGetA(border)) | 515 "min-width", &w, "min-height", &h, nullptr); |
532 return border; | 516 GtkBorder border, padding; |
| 517 GtkStateFlags state = gtk_style_context_get_state(context); |
| 518 gtk_style_context_get_border(context, state, &border); |
| 519 gtk_style_context_get_padding(context, state, &padding); |
| 520 w += border.left + padding.left + padding.right + border.right; |
| 521 h += border.top + padding.top + padding.bottom + border.bottom; |
533 | 522 |
534 return GetBgColor(css_selector); | 523 bool horizontal = gtk_style_context_has_class(context, "horizontal"); |
| 524 if (horizontal) { |
| 525 w = 24; |
| 526 h = std::max(h, 1); |
| 527 } else { |
| 528 DCHECK(gtk_style_context_has_class(context, "vertical")); |
| 529 h = 24; |
| 530 w = std::max(w, 1); |
| 531 } |
| 532 |
| 533 CairoSurface surface(gfx::Size(w, h)); |
| 534 gtk_render_background(context, surface.cairo(), 0, 0, w, h); |
| 535 gtk_render_frame(context, surface.cairo(), 0, 0, w, h); |
| 536 return surface.GetAveragePixelValue(false); |
535 } | 537 } |
536 #endif | 538 #endif |
537 | 539 |
538 } // namespace libgtkui | 540 } // namespace libgtkui |
OLD | NEW |