native_theme_gtk.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Copyright 2016 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. #include "ui/gtk/native_theme_gtk.h"
  5. #include "base/no_destructor.h"
  6. #include "base/strings/strcat.h"
  7. #include "cc/paint/paint_canvas.h"
  8. #include "ui/color/color_id.h"
  9. #include "ui/color/color_provider.h"
  10. #include "ui/color/color_provider_manager.h"
  11. #include "ui/gfx/color_palette.h"
  12. #include "ui/gfx/color_utils.h"
  13. #include "ui/gfx/geometry/rect.h"
  14. #include "ui/gfx/geometry/skia_conversions.h"
  15. #include "ui/gtk/gtk_color_mixers.h"
  16. #include "ui/gtk/gtk_compat.h"
  17. #include "ui/gtk/gtk_util.h"
  18. #include "ui/native_theme/common_theme.h"
  19. #include "ui/native_theme/native_theme_aura.h"
  20. #include "ui/native_theme/native_theme_utils.h"
  21. #include "third_party/skia/include/core/SkBitmap.h"
  22. using base::StrCat;
  23. namespace gtk {
  24. namespace {
  25. enum BackgroundRenderMode {
  26. BG_RENDER_NORMAL,
  27. BG_RENDER_NONE,
  28. BG_RENDER_RECURSIVE,
  29. };
  30. SkBitmap GetWidgetBitmap(const gfx::Size& size,
  31. GtkCssContext context,
  32. BackgroundRenderMode bg_mode,
  33. bool render_frame) {
  34. DCHECK(bg_mode != BG_RENDER_NONE || render_frame);
  35. SkBitmap bitmap;
  36. bitmap.allocN32Pixels(size.width(), size.height());
  37. bitmap.eraseColor(0);
  38. CairoSurface surface(bitmap);
  39. cairo_t* cr = surface.cairo();
  40. switch (bg_mode) {
  41. case BG_RENDER_NORMAL:
  42. gtk_render_background(context, cr, 0, 0, size.width(), size.height());
  43. break;
  44. case BG_RENDER_RECURSIVE:
  45. RenderBackground(size, cr, context);
  46. break;
  47. case BG_RENDER_NONE:
  48. break;
  49. }
  50. if (render_frame)
  51. gtk_render_frame(context, cr, 0, 0, size.width(), size.height());
  52. bitmap.setImmutable();
  53. return bitmap;
  54. }
  55. void PaintWidget(cc::PaintCanvas* canvas,
  56. const gfx::Rect& rect,
  57. GtkCssContext context,
  58. BackgroundRenderMode bg_mode,
  59. bool render_frame) {
  60. canvas->drawImage(cc::PaintImage::CreateFromBitmap(GetWidgetBitmap(
  61. rect.size(), context, bg_mode, render_frame)),
  62. rect.x(), rect.y());
  63. }
  64. } // namespace
  65. // static
  66. NativeThemeGtk* NativeThemeGtk::instance() {
  67. static base::NoDestructor<NativeThemeGtk> s_native_theme;
  68. return s_native_theme.get();
  69. }
  70. NativeThemeGtk::NativeThemeGtk()
  71. : NativeThemeBase(/*should_only_use_dark_colors=*/false,
  72. /*is_custom_system_theme=*/true) {
  73. // g_type_from_name() is only used in GTK3.
  74. if (!GtkCheckVersion(4)) {
  75. // These types are needed by g_type_from_name(), but may not be registered
  76. // at this point. We need the g_type_class magic to make sure the compiler
  77. // doesn't optimize away this code.
  78. g_type_class_unref(g_type_class_ref(gtk_button_get_type()));
  79. g_type_class_unref(g_type_class_ref(gtk_entry_get_type()));
  80. g_type_class_unref(g_type_class_ref(gtk_frame_get_type()));
  81. g_type_class_unref(g_type_class_ref(gtk_header_bar_get_type()));
  82. g_type_class_unref(g_type_class_ref(gtk_image_get_type()));
  83. g_type_class_unref(g_type_class_ref(gtk_info_bar_get_type()));
  84. g_type_class_unref(g_type_class_ref(gtk_label_get_type()));
  85. g_type_class_unref(g_type_class_ref(gtk_menu_get_type()));
  86. g_type_class_unref(g_type_class_ref(gtk_menu_bar_get_type()));
  87. g_type_class_unref(g_type_class_ref(gtk_menu_item_get_type()));
  88. g_type_class_unref(g_type_class_ref(gtk_range_get_type()));
  89. g_type_class_unref(g_type_class_ref(gtk_scrollbar_get_type()));
  90. g_type_class_unref(g_type_class_ref(gtk_scrolled_window_get_type()));
  91. g_type_class_unref(g_type_class_ref(gtk_separator_get_type()));
  92. g_type_class_unref(g_type_class_ref(gtk_spinner_get_type()));
  93. g_type_class_unref(g_type_class_ref(gtk_text_view_get_type()));
  94. g_type_class_unref(g_type_class_ref(gtk_toggle_button_get_type()));
  95. g_type_class_unref(g_type_class_ref(gtk_tree_view_get_type()));
  96. g_type_class_unref(g_type_class_ref(gtk_window_get_type()));
  97. g_type_class_unref(g_type_class_ref(gtk_combo_box_text_get_type()));
  98. g_type_class_unref(g_type_class_ref(gtk_cell_view_get_type()));
  99. g_type_class_unref(g_type_class_ref(gtk_scale_get_type()));
  100. // Initialize the GtkTreeMenu type. _gtk_tree_menu_get_type() is private,
  101. // so we need to initialize it indirectly.
  102. auto model = TakeGObject(GTK_TREE_MODEL(GtkTreeStoreNew(G_TYPE_STRING)));
  103. auto combo = TakeGObject(gtk_combo_box_new_with_model(model));
  104. }
  105. ui::ColorProviderManager::Get().AppendColorProviderInitializer(
  106. base::BindRepeating(AddGtkNativeColorMixer));
  107. OnThemeChanged(gtk_settings_get_default(), nullptr);
  108. }
  109. NativeThemeGtk::~NativeThemeGtk() {
  110. NOTREACHED();
  111. }
  112. void NativeThemeGtk::SetThemeCssOverride(ScopedCssProvider provider) {
  113. if (theme_css_override_) {
  114. if (GtkCheckVersion(4)) {
  115. gtk_style_context_remove_provider_for_display(
  116. gdk_display_get_default(),
  117. GTK_STYLE_PROVIDER(theme_css_override_.get()));
  118. } else {
  119. gtk_style_context_remove_provider_for_screen(
  120. gdk_screen_get_default(),
  121. GTK_STYLE_PROVIDER(theme_css_override_.get()));
  122. }
  123. }
  124. theme_css_override_ = std::move(provider);
  125. if (theme_css_override_) {
  126. if (GtkCheckVersion(4)) {
  127. gtk_style_context_add_provider_for_display(
  128. gdk_display_get_default(),
  129. GTK_STYLE_PROVIDER(theme_css_override_.get()),
  130. GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  131. } else {
  132. gtk_style_context_add_provider_for_screen(
  133. gdk_screen_get_default(),
  134. GTK_STYLE_PROVIDER(theme_css_override_.get()),
  135. GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  136. }
  137. }
  138. }
  139. void NativeThemeGtk::NotifyOnNativeThemeUpdated() {
  140. NativeTheme::NotifyOnNativeThemeUpdated();
  141. // Update the preferred contrast settings for the NativeThemeAura instance and
  142. // notify its observers about the change.
  143. ui::NativeTheme* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
  144. native_theme->set_preferred_contrast(
  145. UserHasContrastPreference()
  146. ? ui::NativeThemeBase::PreferredContrast::kMore
  147. : ui::NativeThemeBase::PreferredContrast::kNoPreference);
  148. native_theme->NotifyOnNativeThemeUpdated();
  149. }
  150. void NativeThemeGtk::OnThemeChanged(GtkSettings* settings,
  151. GtkParamSpec* param) {
  152. SetThemeCssOverride(ScopedCssProvider());
  153. // Hack to workaround a bug on GNOME standard themes which would
  154. // cause black patches to be rendered on GtkFileChooser dialogs.
  155. std::string theme_name =
  156. GetGtkSettingsStringProperty(settings, "gtk-theme-name");
  157. if (!GtkCheckVersion(3, 14)) {
  158. if (theme_name == "Adwaita") {
  159. SetThemeCssOverride(GetCssProvider(
  160. "GtkFileChooser GtkPaned { background-color: @theme_bg_color; }"));
  161. } else if (theme_name == "HighContrast") {
  162. SetThemeCssOverride(GetCssProvider(
  163. "GtkFileChooser GtkPaned { background-color: @theme_base_color; }"));
  164. }
  165. }
  166. // GTK has a dark mode setting called "gtk-application-prefer-dark-theme", but
  167. // this is really only used for themes that have a dark or light variant that
  168. // gets toggled based on this setting (eg. Adwaita). Most dark themes do not
  169. // have a light variant and aren't affected by the setting. Because of this,
  170. // experimentally check if the theme is dark by checking if the window
  171. // background color is dark.
  172. const SkColor window_bg_color = GetBgColor("");
  173. set_use_dark_colors(IsForcedDarkMode() ||
  174. color_utils::IsDark(window_bg_color));
  175. set_preferred_color_scheme(CalculatePreferredColorScheme());
  176. // GTK doesn't have a native high contrast setting. Rather, it's implied by
  177. // the theme name. The only high contrast GTK themes that I know of are
  178. // HighContrast (GNOME) and ContrastHighInverse (MATE). So infer the contrast
  179. // based on if the theme name contains both "high" and "contrast",
  180. // case-insensitive.
  181. std::transform(theme_name.begin(), theme_name.end(), theme_name.begin(),
  182. ::tolower);
  183. bool high_contrast = theme_name.find("high") != std::string::npos &&
  184. theme_name.find("contrast") != std::string::npos;
  185. set_preferred_contrast(
  186. high_contrast ? ui::NativeThemeBase::PreferredContrast::kMore
  187. : ui::NativeThemeBase::PreferredContrast::kNoPreference);
  188. NotifyOnNativeThemeUpdated();
  189. }
  190. void NativeThemeGtk::PaintMenuPopupBackground(
  191. cc::PaintCanvas* canvas,
  192. const ui::ColorProvider* color_provider,
  193. const gfx::Size& size,
  194. const MenuBackgroundExtraParams& menu_background,
  195. ColorScheme color_scheme) const {
  196. auto context = GetStyleContextFromCss(GtkCssMenu());
  197. // Chrome menus aren't rendered with transparency, so avoid rounded corners.
  198. ApplyCssToContext(context, "* { border-radius: 0px; }");
  199. PaintWidget(canvas, gfx::Rect(size), context, BG_RENDER_RECURSIVE, false);
  200. }
  201. void NativeThemeGtk::PaintMenuItemBackground(
  202. cc::PaintCanvas* canvas,
  203. const ui::ColorProvider* color_provider,
  204. State state,
  205. const gfx::Rect& rect,
  206. const MenuItemExtraParams& menu_item,
  207. ColorScheme color_scheme) const {
  208. auto context =
  209. GetStyleContextFromCss(StrCat({GtkCssMenu(), " ", GtkCssMenuItem()}));
  210. gtk_style_context_set_state(context, StateToStateFlags(state));
  211. PaintWidget(canvas, rect, context, BG_RENDER_NORMAL, true);
  212. }
  213. void NativeThemeGtk::PaintMenuSeparator(
  214. cc::PaintCanvas* canvas,
  215. const ui::ColorProvider* color_provider,
  216. State state,
  217. const gfx::Rect& rect,
  218. const MenuSeparatorExtraParams& menu_separator) const {
  219. // TODO(estade): use GTK to draw vertical separators too. See
  220. // crbug.com/710183
  221. if (menu_separator.type == ui::VERTICAL_SEPARATOR) {
  222. cc::PaintFlags paint;
  223. paint.setStyle(cc::PaintFlags::kFill_Style);
  224. DCHECK(color_provider);
  225. paint.setColor(color_provider->GetColor(ui::kColorMenuSeparator));
  226. canvas->drawRect(gfx::RectToSkRect(rect), paint);
  227. return;
  228. }
  229. auto separator_offset = [&](int separator_thickness) {
  230. switch (menu_separator.type) {
  231. case ui::LOWER_SEPARATOR:
  232. return rect.height() - separator_thickness;
  233. case ui::UPPER_SEPARATOR:
  234. return 0;
  235. default:
  236. return (rect.height() - separator_thickness) / 2;
  237. }
  238. };
  239. if (GtkCheckVersion(3, 20)) {
  240. auto context = GetStyleContextFromCss(
  241. StrCat({GtkCssMenu(), " GtkSeparator#separator.horizontal"}));
  242. int min_height = 1;
  243. auto margin = GtkStyleContextGetMargin(context);
  244. auto border = GtkStyleContextGetBorder(context);
  245. auto padding = GtkStyleContextGetPadding(context);
  246. if (GtkCheckVersion(4))
  247. min_height = GetSeparatorSize(true).height();
  248. else
  249. GtkStyleContextGet(context, "min-height", &min_height, nullptr);
  250. int w = rect.width() - margin.left() - margin.right();
  251. int h = std::max(min_height + padding.top() + padding.bottom() +
  252. border.top() + border.bottom(),
  253. 1);
  254. int x = margin.left();
  255. int y = separator_offset(h);
  256. PaintWidget(canvas, gfx::Rect(x, y, w, h), context, BG_RENDER_NORMAL, true);
  257. } else {
  258. auto context = GetStyleContextFromCss(
  259. StrCat({GtkCssMenu(), " ", GtkCssMenuItem(), ".separator.horizontal"}));
  260. gboolean wide_separators = false;
  261. gint separator_height = 0;
  262. GtkStyleContextGetStyle(context, "wide-separators", &wide_separators,
  263. "separator-height", &separator_height, nullptr);
  264. // This code was adapted from gtk/gtkmenuitem.c. For some reason,
  265. // padding is used as the margin.
  266. auto padding = GtkStyleContextGetPadding(context);
  267. int w = rect.width() - padding.left() - padding.right();
  268. int x = rect.x() + padding.left();
  269. int h = wide_separators ? separator_height : 1;
  270. int y = rect.y() + separator_offset(h);
  271. if (wide_separators) {
  272. PaintWidget(canvas, gfx::Rect(x, y, w, h), context, BG_RENDER_NONE, true);
  273. } else {
  274. cc::PaintFlags flags;
  275. flags.setColor(GtkStyleContextGetColor(context));
  276. flags.setAntiAlias(true);
  277. flags.setStrokeWidth(1);
  278. canvas->drawLine(x + 0.5f, y + 0.5f, x + w + 0.5f, y + 0.5f, flags);
  279. }
  280. }
  281. }
  282. void NativeThemeGtk::PaintFrameTopArea(
  283. cc::PaintCanvas* canvas,
  284. State state,
  285. const gfx::Rect& rect,
  286. const FrameTopAreaExtraParams& frame_top_area,
  287. ColorScheme color_scheme) const {
  288. auto context = GetStyleContextFromCss(frame_top_area.use_custom_frame
  289. ? "#headerbar.header-bar.titlebar"
  290. : "GtkMenuBar#menubar");
  291. ApplyCssToContext(context, "* { border-radius: 0px; border-style: none; }");
  292. gtk_style_context_set_state(context, frame_top_area.is_active
  293. ? GTK_STATE_FLAG_NORMAL
  294. : GTK_STATE_FLAG_BACKDROP);
  295. SkBitmap bitmap =
  296. GetWidgetBitmap(rect.size(), context, BG_RENDER_RECURSIVE, false);
  297. bitmap.setImmutable();
  298. canvas->drawImage(cc::PaintImage::CreateFromBitmap(std::move(bitmap)),
  299. rect.x(), rect.y());
  300. }
  301. } // namespace gtk