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