1// Copyright (c) 2013 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 "chrome/browser/ui/libgtk2ui/native_theme_gtk2.h" 6 7#include <gtk/gtk.h> 8 9#include "chrome/browser/ui/libgtk2ui/chrome_gtk_menu_subclasses.h" 10#include "chrome/browser/ui/libgtk2ui/gtk2_util.h" 11#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" 12#include "ui/gfx/color_utils.h" 13#include "ui/gfx/path.h" 14#include "ui/gfx/rect.h" 15#include "ui/gfx/size.h" 16#include "ui/gfx/skia_util.h" 17#include "ui/native_theme/common_theme.h" 18 19namespace { 20 21// Theme colors returned by GetSystemColor(). 22const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128); 23 24const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00); 25 26GdkColor GdkAlphaBlend(GdkColor foreground, 27 GdkColor background, 28 SkAlpha alpha) { 29 return libgtk2ui::SkColorToGdkColor( 30 color_utils::AlphaBlend(libgtk2ui::GdkColorToSkColor(foreground), 31 libgtk2ui::GdkColorToSkColor(background), alpha)); 32} 33 34// Generates the normal URL color, a green color used in unhighlighted URL 35// text. It is a mix of |kURLTextColor| and the current text color. Unlike the 36// selected text color, it is more important to match the qualities of the 37// foreground typeface color instead of taking the background into account. 38GdkColor NormalURLColor(GdkColor foreground) { 39 color_utils::HSL fg_hsl; 40 color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(foreground), &fg_hsl); 41 42 color_utils::HSL hue_hsl; 43 color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(kURLTextColor), 44 &hue_hsl); 45 46 // Only allow colors that have a fair amount of saturation in them (color vs 47 // white). This means that our output color will always be fairly green. 48 double s = std::max(0.5, fg_hsl.s); 49 50 // Make sure the luminance is at least as bright as the |kURLTextColor| green 51 // would be if we were to use that. 52 double l; 53 if (fg_hsl.l < hue_hsl.l) 54 l = hue_hsl.l; 55 else 56 l = (fg_hsl.l + hue_hsl.l) / 2; 57 58 color_utils::HSL output = { hue_hsl.h, s, l }; 59 return libgtk2ui::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); 60} 61 62// Generates the selected URL color, a green color used on URL text in the 63// currently highlighted entry in the autocomplete popup. It's a mix of 64// |kURLTextColor|, the current text color, and the background color (the 65// select highlight). It is more important to contrast with the background 66// saturation than to look exactly like the foreground color. 67GdkColor SelectedURLColor(GdkColor foreground, GdkColor background) { 68 color_utils::HSL fg_hsl; 69 color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(foreground), 70 &fg_hsl); 71 72 color_utils::HSL bg_hsl; 73 color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(background), 74 &bg_hsl); 75 76 color_utils::HSL hue_hsl; 77 color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(kURLTextColor), 78 &hue_hsl); 79 80 // The saturation of the text should be opposite of the background, clamped 81 // to 0.2-0.8. We make sure it's greater than 0.2 so there's some color, but 82 // less than 0.8 so it's not the oversaturated neon-color. 83 double opposite_s = 1 - bg_hsl.s; 84 double s = std::max(0.2, std::min(0.8, opposite_s)); 85 86 // The luminance should match the luminance of the foreground text. Again, 87 // we clamp so as to have at some amount of color (green) in the text. 88 double opposite_l = fg_hsl.l; 89 double l = std::max(0.1, std::min(0.9, opposite_l)); 90 91 color_utils::HSL output = { hue_hsl.h, s, l }; 92 return libgtk2ui::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); 93} 94 95} // namespace 96 97 98namespace libgtk2ui { 99 100// static 101NativeThemeGtk2* NativeThemeGtk2::instance() { 102 CR_DEFINE_STATIC_LOCAL(NativeThemeGtk2, s_native_theme, ()); 103 return &s_native_theme; 104} 105 106NativeThemeGtk2::NativeThemeGtk2() 107 : fake_window_(NULL), 108 fake_tooltip_(NULL), 109 fake_menu_item_(NULL) { 110} 111 112NativeThemeGtk2::~NativeThemeGtk2() { 113 if (fake_window_) 114 gtk_widget_destroy(fake_window_); 115 if (fake_tooltip_) 116 gtk_widget_destroy(fake_tooltip_); 117 118 fake_entry_.Destroy(); 119 fake_label_.Destroy(); 120 fake_button_.Destroy(); 121 fake_tree_.Destroy(); 122 fake_menu_.Destroy(); 123} 124 125gfx::Size NativeThemeGtk2::GetPartSize(Part part, 126 State state, 127 const ExtraParams& extra) const { 128 if (part == kComboboxArrow) 129 return gfx::Size(12, 12); 130 131 return ui::NativeThemeBase::GetPartSize(part, state, extra); 132} 133 134void NativeThemeGtk2::Paint(SkCanvas* canvas, 135 Part part, 136 State state, 137 const gfx::Rect& rect, 138 const ExtraParams& extra) const { 139 if (rect.IsEmpty()) 140 return; 141 142 switch (part) { 143 case kComboboxArrow: 144 PaintComboboxArrow(canvas, GetGtkState(state), rect); 145 return; 146 147 default: 148 NativeThemeBase::Paint(canvas, part, state, rect, extra); 149 } 150} 151 152SkColor NativeThemeGtk2::GetSystemColor(ColorId color_id) const { 153 return GdkColorToSkColor(GetSystemGdkColor(color_id)); 154} 155 156void NativeThemeGtk2::PaintMenuPopupBackground( 157 SkCanvas* canvas, 158 const gfx::Size& size, 159 const MenuBackgroundExtraParams& menu_background) const { 160 if (menu_background.corner_radius > 0) { 161 SkPaint paint; 162 paint.setStyle(SkPaint::kFill_Style); 163 paint.setFlags(SkPaint::kAntiAlias_Flag); 164 paint.setColor(GetSystemColor(kColorId_MenuBackgroundColor)); 165 166 gfx::Path path; 167 SkRect rect = SkRect::MakeWH(SkIntToScalar(size.width()), 168 SkIntToScalar(size.height())); 169 SkScalar radius = SkIntToScalar(menu_background.corner_radius); 170 SkScalar radii[8] = {radius, radius, radius, radius, 171 radius, radius, radius, radius}; 172 path.addRoundRect(rect, radii); 173 174 canvas->drawPath(path, paint); 175 } else { 176 canvas->drawColor(GetSystemColor(kColorId_MenuBackgroundColor), 177 SkXfermode::kSrc_Mode); 178 } 179} 180 181void NativeThemeGtk2::PaintMenuItemBackground( 182 SkCanvas* canvas, 183 State state, 184 const gfx::Rect& rect, 185 const MenuListExtraParams& menu_list) const { 186 SkColor color; 187 SkPaint paint; 188 switch (state) { 189 case NativeTheme::kNormal: 190 case NativeTheme::kDisabled: 191 color = GetSystemColor(NativeTheme::kColorId_MenuBackgroundColor); 192 paint.setColor(color); 193 break; 194 case NativeTheme::kHovered: 195 color = GetSystemColor( 196 NativeTheme::kColorId_FocusedMenuItemBackgroundColor); 197 paint.setColor(color); 198 break; 199 default: 200 NOTREACHED() << "Invalid state " << state; 201 break; 202 } 203 canvas->drawRect(gfx::RectToSkRect(rect), paint); 204} 205 206GdkColor NativeThemeGtk2::GetSystemGdkColor(ColorId color_id) const { 207 switch (color_id) { 208 // Windows 209 case kColorId_WindowBackground: 210 return GetWindowStyle()->bg[GTK_STATE_NORMAL]; 211 212 // Dialogs 213 case kColorId_DialogBackground: 214 return GetWindowStyle()->bg[GTK_STATE_NORMAL]; 215 216 // FocusableBorder 217 case kColorId_FocusedBorderColor: 218 return GetEntryStyle()->bg[GTK_STATE_SELECTED]; 219 case kColorId_UnfocusedBorderColor: 220 return GetEntryStyle()->text_aa[GTK_STATE_NORMAL]; 221 222 // MenuItem 223 case kColorId_EnabledMenuItemForegroundColor: 224 case kColorId_DisabledEmphasizedMenuItemForegroundColor: 225 return GetMenuItemStyle()->text[GTK_STATE_NORMAL]; 226 case kColorId_DisabledMenuItemForegroundColor: 227 return GetMenuItemStyle()->text[GTK_STATE_INSENSITIVE]; 228 case kColorId_SelectedMenuItemForegroundColor: 229 return GetMenuItemStyle()->text[GTK_STATE_SELECTED]; 230 case kColorId_FocusedMenuItemBackgroundColor: 231 return GetMenuItemStyle()->bg[GTK_STATE_SELECTED]; 232 case kColorId_HoverMenuItemBackgroundColor: 233 return GetMenuItemStyle()->bg[GTK_STATE_PRELIGHT]; 234 case kColorId_FocusedMenuButtonBorderColor: 235 return GetEntryStyle()->bg[GTK_STATE_NORMAL]; 236 case kColorId_HoverMenuButtonBorderColor: 237 return GetEntryStyle()->text_aa[GTK_STATE_PRELIGHT]; 238 case kColorId_MenuBorderColor: 239 case kColorId_EnabledMenuButtonBorderColor: 240 case kColorId_MenuSeparatorColor: { 241 return GetMenuItemStyle()->text[GTK_STATE_INSENSITIVE]; 242 } 243 case kColorId_MenuBackgroundColor: 244 return GetMenuStyle()->bg[GTK_STATE_NORMAL]; 245 246 // Label 247 case kColorId_LabelEnabledColor: 248 return GetLabelStyle()->text[GTK_STATE_NORMAL]; 249 case kColorId_LabelDisabledColor: 250 return GetLabelStyle()->text[GTK_STATE_INSENSITIVE]; 251 case kColorId_LabelBackgroundColor: 252 return GetWindowStyle()->bg[GTK_STATE_NORMAL]; 253 254 // Button 255 case kColorId_ButtonBackgroundColor: 256 return GetButtonStyle()->bg[GTK_STATE_NORMAL]; 257 case kColorId_ButtonEnabledColor: 258 return GetButtonStyle()->text[GTK_STATE_NORMAL]; 259 case kColorId_ButtonDisabledColor: 260 return GetButtonStyle()->text[GTK_STATE_INSENSITIVE]; 261 case kColorId_ButtonHighlightColor: 262 return GetButtonStyle()->base[GTK_STATE_SELECTED]; 263 case kColorId_ButtonHoverColor: 264 return GetButtonStyle()->text[GTK_STATE_PRELIGHT]; 265 case kColorId_ButtonHoverBackgroundColor: 266 return GetButtonStyle()->bg[GTK_STATE_PRELIGHT]; 267 // TODO(estade): determine a more distinct color for the Blue 268 // buttons. 269 case kColorId_BlueButtonEnabledColor: 270 return GetButtonStyle()->text[GTK_STATE_NORMAL]; 271 case kColorId_BlueButtonDisabledColor: 272 return GetButtonStyle()->text[GTK_STATE_INSENSITIVE]; 273 case kColorId_BlueButtonHighlightColor: 274 return GetButtonStyle()->base[GTK_STATE_SELECTED]; 275 case kColorId_BlueButtonHoverColor: 276 return GetButtonStyle()->text[GTK_STATE_PRELIGHT]; 277 278 // Textfield 279 case kColorId_TextfieldDefaultColor: 280 return GetEntryStyle()->text[GTK_STATE_NORMAL]; 281 case kColorId_TextfieldDefaultBackground: 282 return GetEntryStyle()->base[GTK_STATE_NORMAL]; 283 case kColorId_TextfieldReadOnlyColor: 284 return GetEntryStyle()->text[GTK_STATE_INSENSITIVE]; 285 case kColorId_TextfieldReadOnlyBackground: 286 return GetEntryStyle()->base[GTK_STATE_INSENSITIVE]; 287 case kColorId_TextfieldSelectionColor: 288 return GetEntryStyle()->text[GTK_STATE_SELECTED]; 289 case kColorId_TextfieldSelectionBackgroundFocused: 290 return GetEntryStyle()->base[GTK_STATE_SELECTED]; 291 292 // Tooltips 293 case kColorId_TooltipBackground: 294 return GetTooltipStyle()->bg[GTK_STATE_NORMAL]; 295 case kColorId_TooltipText: 296 return GetTooltipStyle()->fg[GTK_STATE_NORMAL]; 297 298 // Trees and Tables (implemented on GTK using the same class) 299 case kColorId_TableBackground: 300 case kColorId_TreeBackground: 301 return GetTreeStyle()->bg[GTK_STATE_NORMAL]; 302 case kColorId_TableText: 303 case kColorId_TreeText: 304 return GetTreeStyle()->text[GTK_STATE_NORMAL]; 305 case kColorId_TableSelectedText: 306 case kColorId_TableSelectedTextUnfocused: 307 case kColorId_TreeSelectedText: 308 case kColorId_TreeSelectedTextUnfocused: 309 return GetTreeStyle()->text[GTK_STATE_SELECTED]; 310 case kColorId_TableSelectionBackgroundFocused: 311 case kColorId_TableSelectionBackgroundUnfocused: 312 case kColorId_TreeSelectionBackgroundFocused: 313 case kColorId_TreeSelectionBackgroundUnfocused: 314 return GetTreeStyle()->bg[GTK_STATE_SELECTED]; 315 case kColorId_TreeArrow: 316 return GetTreeStyle()->fg[GTK_STATE_NORMAL]; 317 case kColorId_TableGroupingIndicatorColor: 318 return GetTreeStyle()->text_aa[GTK_STATE_NORMAL]; 319 320 // Results Table 321 case kColorId_ResultsTableNormalBackground: 322 return GetEntryStyle()->base[GTK_STATE_NORMAL]; 323 case kColorId_ResultsTableHoveredBackground: { 324 GtkStyle* entry_style = GetEntryStyle(); 325 return GdkAlphaBlend( 326 entry_style->base[GTK_STATE_NORMAL], 327 entry_style->base[GTK_STATE_SELECTED], 0x80); 328 } 329 case kColorId_ResultsTableSelectedBackground: 330 return GetEntryStyle()->base[GTK_STATE_SELECTED]; 331 case kColorId_ResultsTableNormalText: 332 case kColorId_ResultsTableHoveredText: 333 return GetEntryStyle()->text[GTK_STATE_NORMAL]; 334 case kColorId_ResultsTableSelectedText: 335 return GetEntryStyle()->text[GTK_STATE_SELECTED]; 336 case kColorId_ResultsTableNormalDimmedText: 337 case kColorId_ResultsTableHoveredDimmedText: { 338 GtkStyle* entry_style = GetEntryStyle(); 339 return GdkAlphaBlend( 340 entry_style->text[GTK_STATE_NORMAL], 341 entry_style->base[GTK_STATE_NORMAL], 0x80); 342 } 343 case kColorId_ResultsTableSelectedDimmedText: { 344 GtkStyle* entry_style = GetEntryStyle(); 345 return GdkAlphaBlend( 346 entry_style->text[GTK_STATE_SELECTED], 347 entry_style->base[GTK_STATE_NORMAL], 0x80); 348 } 349 case kColorId_ResultsTableNormalUrl: 350 case kColorId_ResultsTableHoveredUrl: { 351 return NormalURLColor(GetEntryStyle()->text[GTK_STATE_NORMAL]); 352 } 353 case kColorId_ResultsTableSelectedUrl: { 354 GtkStyle* entry_style = GetEntryStyle(); 355 return SelectedURLColor(entry_style->text[GTK_STATE_SELECTED], 356 entry_style->base[GTK_STATE_SELECTED]); 357 } 358 case kColorId_ResultsTableNormalDivider: { 359 GtkStyle* win_style = GetWindowStyle(); 360 return GdkAlphaBlend(win_style->text[GTK_STATE_NORMAL], 361 win_style->bg[GTK_STATE_NORMAL], 0x34); 362 } 363 case kColorId_ResultsTableHoveredDivider: { 364 GtkStyle* win_style = GetWindowStyle(); 365 return GdkAlphaBlend(win_style->text[GTK_STATE_PRELIGHT], 366 win_style->bg[GTK_STATE_PRELIGHT], 0x34); 367 } 368 case kColorId_ResultsTableSelectedDivider: { 369 GtkStyle* win_style = GetWindowStyle(); 370 return GdkAlphaBlend(win_style->text[GTK_STATE_SELECTED], 371 win_style->bg[GTK_STATE_SELECTED], 0x34); 372 } 373 } 374 375 return SkColorToGdkColor(kInvalidColorIdColor); 376} 377 378GtkWidget* NativeThemeGtk2::GetRealizedWindow() const { 379 if (!fake_window_) { 380 fake_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); 381 gtk_widget_realize(fake_window_); 382 } 383 384 return fake_window_; 385} 386 387GtkStyle* NativeThemeGtk2::GetWindowStyle() const { 388 return gtk_rc_get_style(GetRealizedWindow()); 389} 390 391GtkStyle* NativeThemeGtk2::GetEntryStyle() const { 392 if (!fake_entry_.get()) { 393 fake_entry_.Own(gtk_entry_new()); 394 395 // The fake entry needs to be in the window so it can be realized so we can 396 // use the computed parts of the style. 397 gtk_container_add(GTK_CONTAINER(GetRealizedWindow()), fake_entry_.get()); 398 gtk_widget_realize(fake_entry_.get()); 399 } 400 return gtk_rc_get_style(fake_entry_.get()); 401} 402 403GtkStyle* NativeThemeGtk2::GetLabelStyle() const { 404 if (!fake_label_.get()) 405 fake_label_.Own(gtk_label_new("")); 406 407 return gtk_rc_get_style(fake_label_.get()); 408} 409 410GtkStyle* NativeThemeGtk2::GetButtonStyle() const { 411 if (!fake_button_.get()) 412 fake_button_.Own(gtk_button_new()); 413 414 return gtk_rc_get_style(fake_button_.get()); 415} 416 417GtkStyle* NativeThemeGtk2::GetTreeStyle() const { 418 if (!fake_tree_.get()) 419 fake_tree_.Own(gtk_tree_view_new()); 420 421 return gtk_rc_get_style(fake_tree_.get()); 422} 423 424GtkStyle* NativeThemeGtk2::GetTooltipStyle() const { 425 if (!fake_tooltip_) { 426 fake_tooltip_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); 427 gtk_widget_set_name(fake_tooltip_, "gtk-tooltip"); 428 gtk_widget_realize(fake_tooltip_); 429 } 430 return gtk_rc_get_style(fake_tooltip_); 431} 432 433GtkStyle* NativeThemeGtk2::GetMenuStyle() const { 434 if (!fake_menu_.get()) 435 fake_menu_.Own(gtk_menu_new()); 436 return gtk_rc_get_style(fake_menu_.get()); 437} 438 439GtkStyle* NativeThemeGtk2::GetMenuItemStyle() const { 440 if (!fake_menu_item_) { 441 if (!fake_menu_.get()) 442 fake_menu_.Own(gtk_custom_menu_new()); 443 444 fake_menu_item_ = gtk_custom_menu_item_new(); 445 gtk_menu_shell_append(GTK_MENU_SHELL(fake_menu_.get()), fake_menu_item_); 446 } 447 448 return gtk_rc_get_style(fake_menu_item_); 449} 450 451void NativeThemeGtk2::PaintComboboxArrow(SkCanvas* canvas, 452 GtkStateType state, 453 const gfx::Rect& rect) const { 454 GdkPixmap* pm = gdk_pixmap_new(gtk_widget_get_window(GetRealizedWindow()), 455 rect.width(), 456 rect.height(), 457 -1); 458 // Paint the background. 459 gtk_paint_flat_box(GetWindowStyle(), 460 pm, 461 state, 462 GTK_SHADOW_NONE, 463 NULL, 464 GetRealizedWindow(), 465 NULL, 0, 0, rect.width(), rect.height()); 466 gtk_paint_arrow(GetWindowStyle(), 467 pm, 468 state, 469 GTK_SHADOW_NONE, 470 NULL, 471 GetRealizedWindow(), 472 NULL, 473 GTK_ARROW_DOWN, 474 true, 475 0, 0, rect.width(), rect.height()); 476 GdkPixbuf* pb = gdk_pixbuf_get_from_drawable(NULL, 477 pm, 478 gdk_drawable_get_colormap(pm), 479 0, 0, 480 0, 0, 481 rect.width(), rect.height()); 482 SkBitmap arrow = GdkPixbufToImageSkia(pb); 483 canvas->drawBitmap(arrow, rect.x(), rect.y()); 484 485 g_object_unref(pb); 486 g_object_unref(pm); 487} 488 489} // namespace libgtk2ui 490