1 | /* |
2 | * Copyright (C) 2007 Apple Inc. |
3 | * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
4 | * Copyright (C) 2008 Collabora Ltd. |
5 | * Copyright (C) 2009 Kenneth Rohde Christiansen |
6 | * Copyright (C) 2010 Igalia S.L. |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Library General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Library General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Library General Public License |
19 | * along with this library; see the file COPYING.LIB. If not, write to |
20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | * Boston, MA 02110-1301, USA. |
22 | * |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "RenderThemeGtk.h" |
27 | |
28 | #include "CSSValueKeywords.h" |
29 | #include "FileList.h" |
30 | #include "FloatRoundedRect.h" |
31 | #include "FontDescription.h" |
32 | #include "GRefPtrGtk.h" |
33 | #include "GUniquePtrGtk.h" |
34 | #include "Gradient.h" |
35 | #include "GraphicsContext.h" |
36 | #include "GtkVersioning.h" |
37 | #include "HTMLInputElement.h" |
38 | #include "HTMLMediaElement.h" |
39 | #include "LocalizedStrings.h" |
40 | #include "MediaControlElements.h" |
41 | #include "Page.h" |
42 | #include "PaintInfo.h" |
43 | #include "PlatformContextCairo.h" |
44 | #include "RenderBox.h" |
45 | #include "RenderObject.h" |
46 | #include "RenderProgress.h" |
47 | #include "RenderThemeWidget.h" |
48 | #include "ScrollbarThemeGtk.h" |
49 | #include "StringTruncator.h" |
50 | #include "TimeRanges.h" |
51 | #include "UserAgentScripts.h" |
52 | #include "UserAgentStyleSheets.h" |
53 | #include <cmath> |
54 | #include <gdk/gdk.h> |
55 | #include <glib.h> |
56 | #include <gtk/gtk.h> |
57 | #include <wtf/FileSystem.h> |
58 | #include <wtf/glib/GRefPtr.h> |
59 | #include <wtf/glib/GUniquePtr.h> |
60 | #include <wtf/text/CString.h> |
61 | #include <wtf/text/StringBuilder.h> |
62 | |
63 | namespace WebCore { |
64 | |
65 | RenderTheme& RenderTheme::singleton() |
66 | { |
67 | static NeverDestroyed<RenderThemeGtk> theme; |
68 | return theme; |
69 | } |
70 | |
71 | static double getScreenDPI() |
72 | { |
73 | // FIXME: Really this should be the widget's screen. |
74 | GdkScreen* screen = gdk_screen_get_default(); |
75 | if (!screen) |
76 | return 96; // Default to 96 DPI. |
77 | |
78 | float dpi = gdk_screen_get_resolution(screen); |
79 | if (dpi <= 0) |
80 | return 96; |
81 | return dpi; |
82 | } |
83 | |
84 | void RenderThemeGtk::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const |
85 | { |
86 | GtkSettings* settings = gtk_settings_get_default(); |
87 | if (!settings) |
88 | return; |
89 | |
90 | // This will be a font selection string like "Sans 10" so we cannot use it as the family name. |
91 | GUniqueOutPtr<gchar> fontName; |
92 | g_object_get(settings, "gtk-font-name" , &fontName.outPtr(), nullptr); |
93 | if (!fontName || !fontName.get()[0]) |
94 | return; |
95 | |
96 | PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get()); |
97 | if (!pangoDescription) |
98 | return; |
99 | |
100 | fontDescription.setOneFamily(pango_font_description_get_family(pangoDescription)); |
101 | |
102 | int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE; |
103 | // If the size of the font is in points, we need to convert it to pixels. |
104 | if (!pango_font_description_get_size_is_absolute(pangoDescription)) |
105 | size = size * (getScreenDPI() / 72.0); |
106 | |
107 | fontDescription.setSpecifiedSize(size); |
108 | fontDescription.setIsAbsoluteSize(true); |
109 | fontDescription.setWeight(normalWeightValue()); |
110 | fontDescription.setItalic(FontSelectionValue()); |
111 | pango_font_description_free(pangoDescription); |
112 | } |
113 | |
114 | #if ENABLE(DATALIST_ELEMENT) |
115 | IntSize RenderThemeGtk::sliderTickSize() const |
116 | { |
117 | // FIXME: We need to set this to the size of one tick mark. |
118 | return IntSize(0, 0); |
119 | } |
120 | |
121 | int RenderThemeGtk::sliderTickOffsetFromTrackCenter() const |
122 | { |
123 | // FIXME: We need to set this to the position of the tick marks. |
124 | return 0; |
125 | } |
126 | #endif |
127 | |
128 | #ifndef GTK_API_VERSION_2 |
129 | |
130 | static void themeChangedCallback() |
131 | { |
132 | Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment(); |
133 | } |
134 | |
135 | RenderThemeGtk::RenderThemeGtk() |
136 | { |
137 | static bool themeMonitorInitialized = false; |
138 | if (!themeMonitorInitialized) { |
139 | GtkSettings* settings = gtk_settings_get_default(); |
140 | g_signal_connect(settings, "notify::gtk-theme-name" , G_CALLBACK(themeChangedCallback), nullptr); |
141 | g_signal_connect(settings, "notify::gtk-color-scheme" , G_CALLBACK(themeChangedCallback), nullptr); |
142 | themeMonitorInitialized = true; |
143 | } |
144 | } |
145 | |
146 | enum RenderThemePart { |
147 | Entry, |
148 | EntrySelection, |
149 | EntryIconLeft, |
150 | EntryIconRight, |
151 | Button, |
152 | CheckButton, |
153 | RadioButton, |
154 | ComboBox, |
155 | ComboBoxButton, |
156 | ComboBoxArrow, |
157 | Scale, |
158 | ScaleTrough, |
159 | ScaleSlider, |
160 | ProgressBar, |
161 | ProgressBarTrough, |
162 | ProgressBarProgress, |
163 | ListBox, |
164 | SpinButton, |
165 | SpinButtonUpButton, |
166 | SpinButtonDownButton, |
167 | #if ENABLE(VIDEO) |
168 | MediaButton, |
169 | #endif |
170 | #if GTK_CHECK_VERSION(3, 20, 0) |
171 | Window, |
172 | #endif |
173 | }; |
174 | |
175 | #if !GTK_CHECK_VERSION(3, 20, 0) |
176 | // This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c. |
177 | static const int minArrowSize = 15; |
178 | // This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c. |
179 | static const int minSpinButtonArrowSize = 6; |
180 | |
181 | static GRefPtr<GtkStyleContext> createStyleContext(RenderThemePart themePart, GtkStyleContext* parent = nullptr) |
182 | { |
183 | GRefPtr<GtkWidgetPath> path = adoptGRef(parent ? gtk_widget_path_copy(gtk_style_context_get_path(parent)) : gtk_widget_path_new()); |
184 | |
185 | switch (themePart) { |
186 | case Entry: |
187 | case EntrySelection: |
188 | gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY); |
189 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_ENTRY); |
190 | break; |
191 | case EntryIconLeft: |
192 | case EntryIconRight: |
193 | gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY); |
194 | break; |
195 | case Button: |
196 | gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON); |
197 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON); |
198 | gtk_widget_path_iter_add_class(path.get(), -1, "text-button" ); |
199 | break; |
200 | case CheckButton: |
201 | gtk_widget_path_append_type(path.get(), GTK_TYPE_CHECK_BUTTON); |
202 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_CHECK); |
203 | break; |
204 | case RadioButton: |
205 | gtk_widget_path_append_type(path.get(), GTK_TYPE_RADIO_BUTTON); |
206 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_RADIO); |
207 | break; |
208 | case ComboBox: |
209 | gtk_widget_path_append_type(path.get(), GTK_TYPE_COMBO_BOX); |
210 | break; |
211 | case ComboBoxButton: |
212 | gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON); |
213 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON); |
214 | gtk_widget_path_iter_add_class(path.get(), -1, "text-button" ); |
215 | gtk_widget_path_iter_add_class(path.get(), -1, "combo" ); |
216 | break; |
217 | case ComboBoxArrow: |
218 | gtk_widget_path_append_type(path.get(), GTK_TYPE_ARROW); |
219 | gtk_widget_path_iter_add_class(path.get(), -1, "arrow" ); |
220 | break; |
221 | case Scale: |
222 | gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE); |
223 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE); |
224 | break; |
225 | case ScaleTrough: |
226 | gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE); |
227 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE); |
228 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH); |
229 | break; |
230 | case ScaleSlider: |
231 | gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE); |
232 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE); |
233 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SLIDER); |
234 | break; |
235 | case ProgressBar: |
236 | gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR); |
237 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR); |
238 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL); |
239 | break; |
240 | case ProgressBarTrough: |
241 | gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR); |
242 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH); |
243 | break; |
244 | case ProgressBarProgress: |
245 | gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR); |
246 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR); |
247 | break; |
248 | case ListBox: |
249 | gtk_widget_path_append_type(path.get(), GTK_TYPE_TREE_VIEW); |
250 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_VIEW); |
251 | break; |
252 | case SpinButton: |
253 | gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON); |
254 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON); |
255 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL); |
256 | break; |
257 | case SpinButtonUpButton: |
258 | case SpinButtonDownButton: |
259 | gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON); |
260 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON); |
261 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON); |
262 | break; |
263 | #if ENABLE(VIDEO) |
264 | case MediaButton: |
265 | gtk_widget_path_append_type(path.get(), GTK_TYPE_IMAGE); |
266 | gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_IMAGE); |
267 | break; |
268 | #endif // ENABLE(VIDEO) |
269 | default: |
270 | ASSERT_NOT_REACHED(); |
271 | break; |
272 | } |
273 | |
274 | GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new()); |
275 | gtk_style_context_set_path(context.get(), path.get()); |
276 | gtk_style_context_set_parent(context.get(), parent); |
277 | return context; |
278 | } |
279 | |
280 | static GRefPtr<GdkPixbuf> loadThemedIcon(GtkStyleContext* context, const char* iconName, GtkIconSize iconSize) |
281 | { |
282 | GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(iconName)); |
283 | unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG; |
284 | #if GTK_CHECK_VERSION(3, 14, 0) |
285 | GtkTextDirection direction = gtk_style_context_get_direction(context); |
286 | if (direction & GTK_TEXT_DIR_LTR) |
287 | lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR; |
288 | else if (direction & GTK_TEXT_DIR_RTL) |
289 | lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL; |
290 | #endif |
291 | int width, height; |
292 | gtk_icon_size_lookup(iconSize, &width, &height); |
293 | GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(), std::min(width, height), static_cast<GtkIconLookupFlags>(lookupFlags))); |
294 | if (!iconInfo) |
295 | return nullptr; |
296 | |
297 | return adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), context, nullptr, nullptr)); |
298 | } |
299 | #endif // !GTK_CHECK_VERSION(3, 20, 0) |
300 | |
301 | #if ENABLE(VIDEO) |
302 | static bool nodeHasPseudo(Node& node, const char* pseudo) |
303 | { |
304 | return is<Element>(node) && downcast<Element>(node).pseudo() == pseudo; |
305 | } |
306 | |
307 | static bool nodeHasClass(Node* node, const char* className) |
308 | { |
309 | if (!is<Element>(*node)) |
310 | return false; |
311 | |
312 | Element& element = downcast<Element>(*node); |
313 | |
314 | if (!element.hasClass()) |
315 | return false; |
316 | |
317 | return element.classNames().contains(className); |
318 | } |
319 | #endif // ENABLE(VIDEO) |
320 | |
321 | RenderThemeGtk::~RenderThemeGtk() = default; |
322 | |
323 | static bool supportsFocus(ControlPart appearance) |
324 | { |
325 | switch (appearance) { |
326 | case PushButtonPart: |
327 | case ButtonPart: |
328 | case TextFieldPart: |
329 | case TextAreaPart: |
330 | case SearchFieldPart: |
331 | case MenulistPart: |
332 | case RadioPart: |
333 | case CheckboxPart: |
334 | case SliderHorizontalPart: |
335 | case SliderVerticalPart: |
336 | return true; |
337 | default: |
338 | return false; |
339 | } |
340 | } |
341 | |
342 | bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const |
343 | { |
344 | return supportsFocus(style.appearance()); |
345 | } |
346 | |
347 | bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const |
348 | { |
349 | return isEnabled(o); |
350 | } |
351 | |
352 | int RenderThemeGtk::baselinePosition(const RenderBox& box) const |
353 | { |
354 | // FIXME: This strategy is possibly incorrect for the GTK+ port. |
355 | if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart) |
356 | return box.marginTop() + box.height() - 2; |
357 | return RenderTheme::baselinePosition(box); |
358 | } |
359 | |
360 | #if GTK_CHECK_VERSION(3, 20, 0) |
361 | void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&) |
362 | { |
363 | } |
364 | static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject) |
365 | { |
366 | unsigned stateFlags = 0; |
367 | switch (renderObject.style().direction()) { |
368 | case TextDirection::RTL: |
369 | stateFlags |= GTK_STATE_FLAG_DIR_RTL; |
370 | break; |
371 | case TextDirection::LTR: |
372 | stateFlags |= GTK_STATE_FLAG_DIR_LTR; |
373 | break; |
374 | } |
375 | |
376 | if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject))) |
377 | stateFlags |= GTK_STATE_FLAG_INSENSITIVE; |
378 | else { |
379 | if (theme.isHovered(renderObject)) |
380 | stateFlags |= GTK_STATE_FLAG_PRELIGHT; |
381 | if (theme.isFocused(renderObject)) |
382 | stateFlags |= GTK_STATE_FLAG_FOCUSED; |
383 | } |
384 | |
385 | switch (themePart) { |
386 | case CheckButton: |
387 | case RadioButton: |
388 | if (theme.isChecked(renderObject)) |
389 | stateFlags |= GTK_STATE_FLAG_CHECKED; |
390 | if (theme.isIndeterminate(renderObject)) |
391 | stateFlags |= GTK_STATE_FLAG_INCONSISTENT; |
392 | if (theme.isPressed(renderObject)) |
393 | stateFlags |= GTK_STATE_FLAG_SELECTED; |
394 | break; |
395 | case Button: |
396 | case ComboBoxButton: |
397 | case ScaleSlider: |
398 | case EntryIconLeft: |
399 | case EntryIconRight: |
400 | #if ENABLE(VIDEO) |
401 | case MediaButton: |
402 | #endif |
403 | if (theme.isPressed(renderObject)) |
404 | stateFlags |= GTK_STATE_FLAG_ACTIVE; |
405 | break; |
406 | case SpinButtonUpButton: |
407 | if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject)) |
408 | stateFlags |= GTK_STATE_FLAG_ACTIVE; |
409 | if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject)) |
410 | stateFlags &= ~GTK_STATE_FLAG_PRELIGHT; |
411 | break; |
412 | case SpinButtonDownButton: |
413 | if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject)) |
414 | stateFlags |= GTK_STATE_FLAG_ACTIVE; |
415 | if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject)) |
416 | stateFlags &= ~GTK_STATE_FLAG_PRELIGHT; |
417 | break; |
418 | default: |
419 | break; |
420 | } |
421 | |
422 | return static_cast<GtkStateFlags>(stateFlags); |
423 | } |
424 | #else |
425 | static GtkTextDirection gtkTextDirection(TextDirection direction) |
426 | { |
427 | switch (direction) { |
428 | case TextDirection::RTL: |
429 | return GTK_TEXT_DIR_RTL; |
430 | case TextDirection::LTR: |
431 | return GTK_TEXT_DIR_LTR; |
432 | default: |
433 | return GTK_TEXT_DIR_NONE; |
434 | } |
435 | } |
436 | |
437 | static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject) |
438 | { |
439 | if (!theme->isEnabled(renderObject)) |
440 | return GTK_STATE_FLAG_INSENSITIVE; |
441 | if (theme->isPressed(renderObject)) |
442 | return GTK_STATE_FLAG_ACTIVE; |
443 | if (theme->isHovered(renderObject)) |
444 | return GTK_STATE_FLAG_PRELIGHT; |
445 | |
446 | return GTK_STATE_FLAG_NORMAL; |
447 | } |
448 | |
449 | static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect) |
450 | { |
451 | gint focusWidth, focusPad; |
452 | gtk_style_context_get_style(context, "focus-line-width" , &focusWidth, "focus-padding" , &focusPad, nullptr); |
453 | rect.inflate(focusWidth + focusPad); |
454 | } |
455 | |
456 | void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect) |
457 | { |
458 | GRefPtr<GtkStyleContext> context; |
459 | bool checkInteriorFocus = false; |
460 | ControlPart part = renderObject.style().appearance(); |
461 | switch (part) { |
462 | case CheckboxPart: |
463 | case RadioPart: |
464 | context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton); |
465 | |
466 | gint indicatorSpacing; |
467 | gtk_style_context_get_style(context.get(), "indicator-spacing" , &indicatorSpacing, nullptr); |
468 | rect.inflate(indicatorSpacing); |
469 | |
470 | return; |
471 | case SliderVerticalPart: |
472 | case SliderHorizontalPart: |
473 | context = createStyleContext(ScaleSlider); |
474 | break; |
475 | case ButtonPart: |
476 | case MenulistButtonPart: |
477 | case MenulistPart: |
478 | context = createStyleContext(Button); |
479 | checkInteriorFocus = true; |
480 | break; |
481 | case TextFieldPart: |
482 | case TextAreaPart: |
483 | context = createStyleContext(Entry); |
484 | checkInteriorFocus = true; |
485 | break; |
486 | default: |
487 | return; |
488 | } |
489 | |
490 | ASSERT(context); |
491 | if (checkInteriorFocus) { |
492 | gboolean interiorFocus; |
493 | gtk_style_context_get_style(context.get(), "interior-focus" , &interiorFocus, nullptr); |
494 | if (interiorFocus) |
495 | return; |
496 | } |
497 | adjustRectForFocus(context.get(), rect); |
498 | } |
499 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
500 | |
501 | void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const |
502 | { |
503 | // Some layout tests check explicitly that buttons ignore line-height. |
504 | if (style.appearance() == PushButtonPart) |
505 | style.setLineHeight(RenderStyle::initialLineHeight()); |
506 | } |
507 | |
508 | static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize) |
509 | { |
510 | if (rect.width() > minSize.width()) { |
511 | rect.inflateX(-(rect.width() - minSize.width()) / 2); |
512 | rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1. |
513 | } |
514 | |
515 | if (rect.height() > minSize.height()) { |
516 | rect.inflateY(-(rect.height() - minSize.height()) / 2); |
517 | rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1. |
518 | } |
519 | } |
520 | |
521 | #if GTK_CHECK_VERSION(3, 20, 0) |
522 | static void setToggleSize(RenderThemePart themePart, RenderStyle& style) |
523 | { |
524 | ASSERT(themePart == CheckButton || themePart == RadioButton); |
525 | |
526 | // The width and height are both specified, so we shouldn't change them. |
527 | if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto()) |
528 | return; |
529 | |
530 | auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton)); |
531 | toggleWidget.button().setState(GTK_STATE_FLAG_NORMAL); |
532 | toggleWidget.toggle().setState(GTK_STATE_FLAG_NORMAL); |
533 | IntSize preferredSize = toggleWidget.button().preferredSize(); |
534 | preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize()); |
535 | |
536 | if (style.width().isIntrinsicOrAuto()) |
537 | style.setWidth(Length(preferredSize.width(), Fixed)); |
538 | |
539 | if (style.height().isAuto()) |
540 | style.setHeight(Length(preferredSize.height(), Fixed)); |
541 | } |
542 | |
543 | static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect) |
544 | { |
545 | ASSERT(themePart == CheckButton || themePart == RadioButton); |
546 | |
547 | auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton)); |
548 | auto toggleState = themePartStateFlags(*theme, themePart, renderObject); |
549 | toggleWidget.button().setState(toggleState); |
550 | toggleWidget.toggle().setState(toggleState); |
551 | |
552 | FloatRect rect = fullRect; |
553 | // Some themes do not render large toggle buttons properly, so we simply |
554 | // shrink the rectangle back down to the default size and then center it |
555 | // in the full toggle button region. The reason for not simply forcing toggle |
556 | // buttons to be a smaller size is that we don't want to break site layouts. |
557 | IntSize preferredSize = toggleWidget.button().preferredSize(); |
558 | preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize()); |
559 | shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize); |
560 | toggleWidget.button().render(paintInfo.context().platformContext()->cr(), rect); |
561 | toggleWidget.toggle().render(paintInfo.context().platformContext()->cr(), rect); |
562 | |
563 | if (theme->isFocused(renderObject)) |
564 | toggleWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect); |
565 | } |
566 | #else |
567 | static void setToggleSize(RenderThemePart themePart, RenderStyle& style) |
568 | { |
569 | // The width and height are both specified, so we shouldn't change them. |
570 | if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto()) |
571 | return; |
572 | |
573 | GRefPtr<GtkStyleContext> context = createStyleContext(themePart); |
574 | // Other ports hard-code this to 13. GTK+ users tend to demand the native look. |
575 | gint indicatorSize; |
576 | gtk_style_context_get_style(context.get(), "indicator-size" , &indicatorSize, nullptr); |
577 | |
578 | if (style.width().isIntrinsicOrAuto()) |
579 | style.setWidth(Length(indicatorSize, Fixed)); |
580 | |
581 | if (style.height().isAuto()) |
582 | style.setHeight(Length(indicatorSize, Fixed)); |
583 | } |
584 | |
585 | static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect) |
586 | { |
587 | GRefPtr<GtkStyleContext> context = createStyleContext(themePart); |
588 | gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()))); |
589 | |
590 | unsigned flags = 0; |
591 | if (!theme->isEnabled(renderObject)) |
592 | flags |= GTK_STATE_FLAG_INSENSITIVE; |
593 | else if (theme->isHovered(renderObject)) |
594 | flags |= GTK_STATE_FLAG_PRELIGHT; |
595 | if (theme->isIndeterminate(renderObject)) |
596 | flags |= GTK_STATE_FLAG_INCONSISTENT; |
597 | else if (theme->isChecked(renderObject)) |
598 | #if GTK_CHECK_VERSION(3, 13, 7) |
599 | flags |= GTK_STATE_FLAG_CHECKED; |
600 | #else |
601 | flags |= GTK_STATE_FLAG_ACTIVE; |
602 | #endif |
603 | if (theme->isPressed(renderObject)) |
604 | flags |= GTK_STATE_FLAG_SELECTED; |
605 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags)); |
606 | |
607 | // Some themes do not render large toggle buttons properly, so we simply |
608 | // shrink the rectangle back down to the default size and then center it |
609 | // in the full toggle button region. The reason for not simply forcing toggle |
610 | // buttons to be a smaller size is that we don't want to break site layouts. |
611 | FloatRect rect(fullRect); |
612 | gint indicatorSize; |
613 | gtk_style_context_get_style(context.get(), "indicator-size" , &indicatorSize, nullptr); |
614 | IntSize minSize(indicatorSize, indicatorSize); |
615 | shrinkToMinimumSizeAndCenterRectangle(rect, minSize); |
616 | |
617 | gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
618 | gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
619 | |
620 | if (themePart == CheckButton) |
621 | gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
622 | else |
623 | gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
624 | |
625 | if (theme->isFocused(renderObject)) { |
626 | IntRect indicatorRect(rect); |
627 | gint indicatorSpacing; |
628 | gtk_style_context_get_style(context.get(), "indicator-spacing" , &indicatorSpacing, nullptr); |
629 | indicatorRect.inflate(indicatorSpacing); |
630 | gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(), |
631 | indicatorRect.width(), indicatorRect.height()); |
632 | } |
633 | } |
634 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
635 | |
636 | void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const |
637 | { |
638 | setToggleSize(CheckButton, style); |
639 | } |
640 | |
641 | bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
642 | { |
643 | paintToggle(this, CheckButton, renderObject, paintInfo, rect); |
644 | return false; |
645 | } |
646 | |
647 | void RenderThemeGtk::setRadioSize(RenderStyle& style) const |
648 | { |
649 | setToggleSize(RadioButton, style); |
650 | } |
651 | |
652 | bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
653 | { |
654 | paintToggle(this, RadioButton, renderObject, paintInfo, rect); |
655 | return false; |
656 | } |
657 | |
658 | #if GTK_CHECK_VERSION(3, 20, 0) |
659 | bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
660 | { |
661 | auto& buttonWidget = static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(isDefault(renderObject) ? RenderThemeWidget::Type::ButtonDefault : RenderThemeWidget::Type::Button)); |
662 | buttonWidget.button().setState(themePartStateFlags(*this, Button, renderObject)); |
663 | buttonWidget.button().render(paintInfo.context().platformContext()->cr(), rect); |
664 | if (isFocused(renderObject)) |
665 | buttonWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect); |
666 | return false; |
667 | } |
668 | #else |
669 | static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
670 | { |
671 | IntRect buttonRect(rect); |
672 | |
673 | guint flags = 0; |
674 | if (!theme->isEnabled(renderObject)) |
675 | flags |= GTK_STATE_FLAG_INSENSITIVE; |
676 | else if (theme->isHovered(renderObject)) |
677 | flags |= GTK_STATE_FLAG_PRELIGHT; |
678 | if (theme->isPressed(renderObject)) |
679 | flags |= GTK_STATE_FLAG_ACTIVE; |
680 | gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags)); |
681 | |
682 | if (theme->isDefault(renderObject)) { |
683 | GtkBorder* borderPtr = 0; |
684 | GtkBorder border = { 1, 1, 1, 1 }; |
685 | |
686 | gtk_style_context_get_style(context, "default-border" , &borderPtr, nullptr); |
687 | if (borderPtr) { |
688 | border = *borderPtr; |
689 | gtk_border_free(borderPtr); |
690 | } |
691 | |
692 | buttonRect.move(border.left, border.top); |
693 | buttonRect.setWidth(buttonRect.width() - (border.left + border.right)); |
694 | buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom)); |
695 | |
696 | gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT); |
697 | } |
698 | |
699 | gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height()); |
700 | gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height()); |
701 | |
702 | if (theme->isFocused(renderObject)) { |
703 | gint focusWidth, focusPad; |
704 | gboolean displaceFocus, interiorFocus; |
705 | gtk_style_context_get_style( |
706 | context, |
707 | "focus-line-width" , &focusWidth, |
708 | "focus-padding" , &focusPad, |
709 | "interior-focus" , &interiorFocus, |
710 | "displace-focus" , &displaceFocus, |
711 | nullptr); |
712 | |
713 | if (interiorFocus) { |
714 | GtkBorder borderWidth; |
715 | gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth); |
716 | |
717 | buttonRect = IntRect( |
718 | buttonRect.x() + borderWidth.left + focusPad, |
719 | buttonRect.y() + borderWidth.top + focusPad, |
720 | buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right), |
721 | buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom)); |
722 | } else |
723 | buttonRect.inflate(focusWidth + focusPad); |
724 | |
725 | if (displaceFocus && theme->isPressed(renderObject)) { |
726 | gint childDisplacementX; |
727 | gint childDisplacementY; |
728 | gtk_style_context_get_style(context, "child-displacement-x" , &childDisplacementX, "child-displacement-y" , &childDisplacementY, nullptr); |
729 | buttonRect.move(childDisplacementX, childDisplacementY); |
730 | } |
731 | |
732 | gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height()); |
733 | } |
734 | } |
735 | bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
736 | { |
737 | GRefPtr<GtkStyleContext> context = createStyleContext(Button); |
738 | gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()))); |
739 | renderButton(this, context.get(), renderObject, paintInfo, rect); |
740 | return false; |
741 | } |
742 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
743 | |
744 | static Color (const Element* element) |
745 | { |
746 | #if GTK_CHECK_VERSION(3, 20, 0) |
747 | auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox)); |
748 | GtkStateFlags state = element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL; |
749 | comboWidget.comboBox().setState(state); |
750 | comboWidget.button().setState(state); |
751 | return comboWidget.button().color(); |
752 | #else |
753 | GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox); |
754 | GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get()); |
755 | gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL); |
756 | |
757 | GdkRGBA gdkRGBAColor; |
758 | gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor); |
759 | return gdkRGBAColor; |
760 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
761 | } |
762 | |
763 | void RenderThemeGtk::(StyleResolver&, RenderStyle& style, const Element* element) const |
764 | { |
765 | // The tests check explicitly that select menu buttons ignore line height. |
766 | style.setLineHeight(RenderStyle::initialLineHeight()); |
767 | |
768 | // We cannot give a proper rendering when border radius is active, unfortunately. |
769 | style.resetBorderRadius(); |
770 | |
771 | if (element) |
772 | style.setColor(menuListColor(element)); |
773 | } |
774 | |
775 | void RenderThemeGtk::(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const |
776 | { |
777 | adjustMenuListStyle(styleResolver, style, e); |
778 | } |
779 | |
780 | #if GTK_CHECK_VERSION(3, 20, 0) |
781 | /* |
782 | * GtkComboBox gadgets tree |
783 | * |
784 | * combobox |
785 | * ├── box.linked |
786 | * │ ╰── button.combo |
787 | * │ ╰── box |
788 | * │ ├── cellview |
789 | * │ ╰── arrow |
790 | * ╰── window.popup |
791 | */ |
792 | LengthBox RenderThemeGtk::(const RenderStyle& style) const |
793 | { |
794 | if (style.appearance() == NoControlPart) |
795 | return LengthBox(0); |
796 | |
797 | auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox)); |
798 | comboWidget.comboBox().setState(GTK_STATE_FLAG_NORMAL); |
799 | comboWidget.button().setState(GTK_STATE_FLAG_NORMAL); |
800 | comboWidget.arrow().setState(GTK_STATE_FLAG_NORMAL); |
801 | GtkBorder comboContentsBox = comboWidget.comboBox().contentsBox(); |
802 | GtkBorder boxContentsBox = comboWidget.box().contentsBox(); |
803 | GtkBorder buttonContentsBox = comboWidget.button().contentsBox(); |
804 | GtkBorder buttonBoxContentsBox = comboWidget.buttonBox().contentsBox(); |
805 | GtkBorder padding; |
806 | padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left; |
807 | padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right; |
808 | padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top; |
809 | padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom; |
810 | |
811 | auto arrowSize = comboWidget.arrow().preferredSize(); |
812 | return LengthBox(padding.top, padding.right + (style.direction() == TextDirection::LTR ? arrowSize.width() : 0), |
813 | padding.bottom, padding.left + (style.direction() == TextDirection::RTL ? arrowSize.width() : 0)); |
814 | } |
815 | |
816 | bool RenderThemeGtk::(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect) |
817 | { |
818 | auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox)); |
819 | auto comboState = themePartStateFlags(*this, ComboBoxButton, renderObject); |
820 | comboWidget.comboBox().setState(comboState); |
821 | comboWidget.button().setState(comboState); |
822 | comboWidget.arrow().setState(comboState); |
823 | |
824 | cairo_t* cr = paintInfo.context().platformContext()->cr(); |
825 | comboWidget.comboBox().render(cr, rect); |
826 | comboWidget.box().render(cr, rect); |
827 | FloatRect contentsRect; |
828 | comboWidget.button().render(cr, rect, &contentsRect); |
829 | comboWidget.buttonBox().render(cr, contentsRect); |
830 | comboWidget.arrow().render(cr, contentsRect); |
831 | if (isFocused(renderObject)) |
832 | comboWidget.button().renderFocus(cr, rect); |
833 | |
834 | return false; |
835 | } |
836 | #else |
837 | LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const |
838 | { |
839 | if (style.appearance() == NoControlPart) |
840 | return { 0, 0, 0, 0 }; |
841 | |
842 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox); |
843 | GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get()); |
844 | gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction()))); |
845 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0)); |
846 | GtkBorder borderWidth = { 0, 0, 0, 0 }; |
847 | gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth); |
848 | |
849 | gboolean interiorFocus; |
850 | gint focusWidth, focusPad; |
851 | gtk_style_context_get_style(context.get(), "interior-focus" , &interiorFocus, "focus-line-width" , &focusWidth, "focus-padding" , &focusPad, nullptr); |
852 | focusWidth = interiorFocus ? focusWidth + focusPad : 0; |
853 | |
854 | return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == TextDirection::LTR ? minArrowSize : 0), |
855 | borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == TextDirection::RTL ? minArrowSize : 0) }; |
856 | } |
857 | |
858 | bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r) |
859 | { |
860 | // FIXME: adopt subpixel themes. |
861 | IntRect rect = IntRect(r); |
862 | |
863 | cairo_t* cairoContext = paintInfo.context().platformContext()->cr(); |
864 | GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())); |
865 | |
866 | GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox); |
867 | |
868 | // Paint the button. |
869 | GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get()); |
870 | gtk_style_context_set_direction(buttonStyleContext.get(), direction); |
871 | renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect); |
872 | |
873 | // Get the inner rectangle. |
874 | gint focusWidth, focusPad; |
875 | GtkBorder* innerBorderPtr = 0; |
876 | GtkBorder innerBorder = { 1, 1, 1, 1 }; |
877 | gtk_style_context_get_style(buttonStyleContext.get(), "inner-border" , &innerBorderPtr, "focus-line-width" , &focusWidth, "focus-padding" , &focusPad, nullptr); |
878 | if (innerBorderPtr) { |
879 | innerBorder = *innerBorderPtr; |
880 | gtk_border_free(innerBorderPtr); |
881 | } |
882 | |
883 | GtkBorder borderWidth; |
884 | GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get()); |
885 | gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth); |
886 | |
887 | focusWidth += focusPad; |
888 | IntRect innerRect( |
889 | rect.x() + innerBorder.left + borderWidth.left + focusWidth, |
890 | rect.y() + innerBorder.top + borderWidth.top + focusWidth, |
891 | rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth), |
892 | rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth)); |
893 | |
894 | if (isPressed(renderObject)) { |
895 | gint childDisplacementX; |
896 | gint childDisplacementY; |
897 | gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x" , &childDisplacementX, "child-displacement-y" , &childDisplacementY, nullptr); |
898 | innerRect.move(childDisplacementX, childDisplacementY); |
899 | } |
900 | innerRect.setWidth(std::max(1, innerRect.width())); |
901 | innerRect.setHeight(std::max(1, innerRect.height())); |
902 | |
903 | // Paint the arrow. |
904 | GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get()); |
905 | gtk_style_context_set_direction(arrowStyleContext.get(), direction); |
906 | |
907 | gfloat arrowScaling; |
908 | gtk_style_context_get_style(parentStyleContext.get(), "arrow-scaling" , &arrowScaling, nullptr); |
909 | |
910 | IntSize arrowSize(minArrowSize, innerRect.height()); |
911 | FloatPoint arrowPosition(innerRect.location()); |
912 | if (direction == GTK_TEXT_DIR_LTR) |
913 | arrowPosition.move(innerRect.width() - arrowSize.width(), 0); |
914 | |
915 | // GTK+ actually fetches the xalign and valign values from the widget, but since we |
916 | // don't have a widget here, we are just using the default xalign and valign values of 0.5. |
917 | gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling; |
918 | arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2); |
919 | |
920 | gtk_style_context_set_state(arrowStyleContext.get(), state); |
921 | gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent); |
922 | |
923 | return false; |
924 | } |
925 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
926 | |
927 | bool RenderThemeGtk::(const RenderBox& object, const PaintInfo& info, const FloatRect& rect) |
928 | { |
929 | return paintMenuList(object, info, rect); |
930 | } |
931 | |
932 | #if GTK_CHECK_VERSION(3, 20, 0) |
933 | |
934 | static IntSize spinButtonSize() |
935 | { |
936 | auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton)); |
937 | spinButtonWidget.spinButton().setState(GTK_STATE_FLAG_NORMAL); |
938 | spinButtonWidget.entry().setState(GTK_STATE_FLAG_NORMAL); |
939 | spinButtonWidget.up().setState(GTK_STATE_FLAG_NORMAL); |
940 | spinButtonWidget.down().setState(GTK_STATE_FLAG_NORMAL); |
941 | |
942 | IntSize preferredSize = spinButtonWidget.spinButton().preferredSize(); |
943 | preferredSize = preferredSize.expandedTo(spinButtonWidget.entry().preferredSize()); |
944 | IntSize upPreferredSize = preferredSize.expandedTo(spinButtonWidget.up().preferredSize()); |
945 | IntSize downPreferredSize = preferredSize.expandedTo(spinButtonWidget.down().preferredSize()); |
946 | |
947 | return IntSize(upPreferredSize.width() + downPreferredSize.width(), std::max(upPreferredSize.height(), downPreferredSize.height())); |
948 | } |
949 | |
950 | |
951 | void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, const Element* element) const |
952 | { |
953 | if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element))) |
954 | return; |
955 | |
956 | style.setMinHeight(Length(spinButtonSize().height(), Fixed)); |
957 | |
958 | // The default theme for the GTK+ port uses very wide spin buttons (66px) compared to what other |
959 | // browsers use (~13 px). And unfortunately, most of the web developers won't test how their site |
960 | // renders on WebKitGTK+. To ensure that spin buttons don't end up covering the values of the input |
961 | // field, we override the width of the input element and always increment it with the width needed |
962 | // for the spinbutton (when drawing the spinbutton). |
963 | int minimumWidth = style.width().intValue() + spinButtonSize().width(); |
964 | style.setMinWidth(Length(minimumWidth, Fixed)); |
965 | } |
966 | |
967 | bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect) |
968 | { |
969 | if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) { |
970 | auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton)); |
971 | auto spinButtonState = themePartStateFlags(*this, Entry, renderObject); |
972 | spinButtonWidget.spinButton().setState(spinButtonState); |
973 | spinButtonWidget.entry().setState(spinButtonState); |
974 | spinButtonWidget.spinButton().render(paintInfo.context().platformContext()->cr(), rect); |
975 | spinButtonWidget.entry().render(paintInfo.context().platformContext()->cr(), rect); |
976 | } else { |
977 | auto& entryWidget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)); |
978 | entryWidget.entry().setState(themePartStateFlags(*this, Entry, renderObject)); |
979 | entryWidget.entry().render(paintInfo.context().platformContext()->cr(), rect); |
980 | } |
981 | return false; |
982 | } |
983 | #else |
984 | void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const |
985 | { |
986 | } |
987 | |
988 | bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect) |
989 | { |
990 | GRefPtr<GtkStyleContext> context = createStyleContext(Entry); |
991 | gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()))); |
992 | |
993 | guint flags = 0; |
994 | if (!isEnabled(renderObject) || isReadOnlyControl(renderObject)) |
995 | flags |= GTK_STATE_FLAG_INSENSITIVE; |
996 | else if (isFocused(renderObject)) |
997 | flags |= GTK_STATE_FLAG_FOCUSED; |
998 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags)); |
999 | |
1000 | gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
1001 | gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
1002 | |
1003 | if (isFocused(renderObject) && isEnabled(renderObject)) { |
1004 | gboolean interiorFocus; |
1005 | gint focusWidth, focusPad; |
1006 | gtk_style_context_get_style(context.get(), "interior-focus" , &interiorFocus, "focus-line-width" , &focusWidth, "focus-padding" , &focusPad, nullptr); |
1007 | if (!interiorFocus) { |
1008 | IntRect focusRect(rect); |
1009 | focusRect.inflate(focusWidth + focusPad); |
1010 | gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height()); |
1011 | } |
1012 | } |
1013 | |
1014 | return false; |
1015 | } |
1016 | #endif |
1017 | |
1018 | #if GTK_CHECK_VERSION(3, 20, 0) |
1019 | static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style) |
1020 | { |
1021 | ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight); |
1022 | auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry)); |
1023 | searchEntryWidget.entry().setState(GTK_STATE_FLAG_NORMAL); |
1024 | searchEntryWidget.leftIcon().setState(GTK_STATE_FLAG_NORMAL); |
1025 | searchEntryWidget.rightIcon().setState(GTK_STATE_FLAG_NORMAL); |
1026 | |
1027 | // Get the icon size based on the font size. |
1028 | auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon()); |
1029 | icon.setIconSize(style.computedFontPixelSize()); |
1030 | IntSize preferredSize = icon.preferredSize(); |
1031 | GtkBorder contentsBox = searchEntryWidget.entry().contentsBox(); |
1032 | if (themePart == EntryIconLeft) |
1033 | preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom); |
1034 | else |
1035 | preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom); |
1036 | style.setWidth(Length(preferredSize.width(), Fixed)); |
1037 | style.setHeight(Length(preferredSize.height(), Fixed)); |
1038 | } |
1039 | #else |
1040 | // Defined in GTK+ (gtk/gtkiconfactory.c) |
1041 | static const gint gtkIconSizeMenu = 16; |
1042 | static const gint gtkIconSizeSmallToolbar = 18; |
1043 | static const gint gtkIconSizeButton = 20; |
1044 | static const gint gtkIconSizeLargeToolbar = 24; |
1045 | static const gint gtkIconSizeDnd = 32; |
1046 | static const gint gtkIconSizeDialog = 48; |
1047 | |
1048 | static GtkIconSize getIconSizeForPixelSize(gint pixelSize) |
1049 | { |
1050 | if (pixelSize < gtkIconSizeSmallToolbar) |
1051 | return GTK_ICON_SIZE_MENU; |
1052 | if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton) |
1053 | return GTK_ICON_SIZE_SMALL_TOOLBAR; |
1054 | if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar) |
1055 | return GTK_ICON_SIZE_BUTTON; |
1056 | if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd) |
1057 | return GTK_ICON_SIZE_LARGE_TOOLBAR; |
1058 | if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog) |
1059 | return GTK_ICON_SIZE_DND; |
1060 | |
1061 | return GTK_ICON_SIZE_DIALOG; |
1062 | } |
1063 | |
1064 | static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style) |
1065 | { |
1066 | style.resetBorder(); |
1067 | style.resetPadding(); |
1068 | |
1069 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry); |
1070 | GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get()); |
1071 | |
1072 | GtkBorder padding; |
1073 | gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding); |
1074 | |
1075 | // Get the icon size based on the font size. |
1076 | int fontSize = style.computedFontPixelSize(); |
1077 | if (fontSize < gtkIconSizeMenu) { |
1078 | style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed)); |
1079 | style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed)); |
1080 | return; |
1081 | } |
1082 | gint width = 0, height = 0; |
1083 | gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height); |
1084 | style.setWidth(Length(width + (padding.left + padding.right), Fixed)); |
1085 | style.setHeight(Length(height + (padding.top + padding.bottom), Fixed)); |
1086 | } |
1087 | #endif |
1088 | |
1089 | bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r) |
1090 | { |
1091 | return paintTextField(o, i, r); |
1092 | } |
1093 | |
1094 | void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const |
1095 | { |
1096 | adjustSearchFieldCancelButtonStyle(styleResolver, style, e); |
1097 | } |
1098 | |
1099 | bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect) |
1100 | { |
1101 | return paintSearchFieldResultsDecorationPart(o, i, rect); |
1102 | } |
1103 | |
1104 | void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1105 | { |
1106 | adjustSearchFieldIconStyle(EntryIconLeft, style); |
1107 | } |
1108 | |
1109 | void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1110 | { |
1111 | adjustSearchFieldIconStyle(EntryIconRight, style); |
1112 | } |
1113 | |
1114 | #if GTK_CHECK_VERSION(3, 20, 0) |
1115 | static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1116 | { |
1117 | ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight); |
1118 | auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry)); |
1119 | searchEntryWidget.entry().setState(themePartStateFlags(*theme, Entry, renderObject)); |
1120 | auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon()); |
1121 | icon.setState(themePartStateFlags(*theme, themePart, renderObject)); |
1122 | icon.setIconSize(renderObject.style().computedFontPixelSize()); |
1123 | GtkBorder contentsBox = searchEntryWidget.entry().contentsBox(); |
1124 | IntRect iconRect = rect; |
1125 | if (themePart == EntryIconLeft) { |
1126 | iconRect.move(contentsBox.left, contentsBox.top); |
1127 | iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom); |
1128 | } else |
1129 | iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom); |
1130 | return !icon.render(paintInfo.context().platformContext()->cr(), iconRect); |
1131 | } |
1132 | bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1133 | { |
1134 | return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect); |
1135 | } |
1136 | |
1137 | bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1138 | { |
1139 | return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect); |
1140 | } |
1141 | #else |
1142 | static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName) |
1143 | { |
1144 | GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height())); |
1145 | if (!icon) |
1146 | return false; |
1147 | |
1148 | if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height()) |
1149 | icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR)); |
1150 | |
1151 | gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y()); |
1152 | return true; |
1153 | } |
1154 | |
1155 | static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state) |
1156 | { |
1157 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry); |
1158 | GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get()); |
1159 | gtk_style_context_set_direction(context.get(), direction); |
1160 | gtk_style_context_set_state(context.get(), state); |
1161 | return paintIcon(context.get(), graphicsContext, rect, iconName); |
1162 | } |
1163 | |
1164 | static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect) |
1165 | { |
1166 | if (!renderObject.node()) |
1167 | return IntRect(); |
1168 | |
1169 | // Get the renderer of <input> element. |
1170 | Node* input = renderObject.node()->shadowHost(); |
1171 | if (!input) |
1172 | input = renderObject.node(); |
1173 | if (!is<RenderBox>(*input->renderer())) |
1174 | return IntRect(); |
1175 | |
1176 | // If possible center the y-coordinate of the rect vertically in the parent input element. |
1177 | // We also add one pixel here to ensure that the y coordinate is rounded up for box heights |
1178 | // that are even, which looks in relation to the box text. |
1179 | IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox(); |
1180 | |
1181 | // Make sure the scaled decoration stays square and will fit in its parent's box. |
1182 | int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height())); |
1183 | IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize); |
1184 | return scaledRect; |
1185 | } |
1186 | |
1187 | bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1188 | { |
1189 | IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect); |
1190 | if (iconRect.isEmpty()) |
1191 | return true; |
1192 | |
1193 | return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic" , paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()), |
1194 | gtkIconStateFlags(this, renderObject)); |
1195 | } |
1196 | |
1197 | bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1198 | { |
1199 | IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect); |
1200 | if (iconRect.isEmpty()) |
1201 | return true; |
1202 | |
1203 | return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic" , paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()), |
1204 | gtkIconStateFlags(this, renderObject)); |
1205 | } |
1206 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
1207 | |
1208 | void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1209 | { |
1210 | // We cannot give a proper rendering when border radius is active, unfortunately. |
1211 | style.resetBorderRadius(); |
1212 | style.setLineHeight(RenderStyle::initialLineHeight()); |
1213 | } |
1214 | |
1215 | bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect) |
1216 | { |
1217 | return paintTextField(o, i, rect); |
1218 | } |
1219 | |
1220 | bool RenderThemeGtk::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const |
1221 | { |
1222 | return element.isPasswordField(); |
1223 | } |
1224 | |
1225 | void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1226 | { |
1227 | style.setBoxShadow(nullptr); |
1228 | } |
1229 | |
1230 | void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const |
1231 | { |
1232 | RenderTheme::adjustSliderThumbStyle(styleResolver, style, element); |
1233 | style.setBoxShadow(nullptr); |
1234 | } |
1235 | |
1236 | #if GTK_CHECK_VERSION(3, 20, 0) |
1237 | /* |
1238 | * GtkScale |
1239 | * |
1240 | * scale |
1241 | * ╰── contents |
1242 | * ╰── trough |
1243 | * ├── slider |
1244 | * ╰── [highlight] |
1245 | */ |
1246 | bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1247 | { |
1248 | ControlPart part = renderObject.style().appearance(); |
1249 | ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart); |
1250 | |
1251 | auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider)); |
1252 | auto scaleState = themePartStateFlags(*this, Scale, renderObject); |
1253 | auto& scale = sliderWidget.scale(); |
1254 | scale.setState(scaleState); |
1255 | auto& contents = sliderWidget.contents(); |
1256 | auto& trough = sliderWidget.trough(); |
1257 | trough.setState(scaleState); |
1258 | auto& slider = sliderWidget.slider(); |
1259 | auto& highlight = sliderWidget.highlight(); |
1260 | |
1261 | // The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it. |
1262 | IntSize preferredSize = scale.preferredSize(); |
1263 | preferredSize = preferredSize.expandedTo(contents.preferredSize()); |
1264 | preferredSize = preferredSize.expandedTo(trough.preferredSize()); |
1265 | FloatRect trackRect = rect; |
1266 | if (part == SliderHorizontalPart) { |
1267 | trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2)); |
1268 | trackRect.setHeight(preferredSize.height()); |
1269 | } else { |
1270 | trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0); |
1271 | trackRect.setWidth(preferredSize.width()); |
1272 | } |
1273 | |
1274 | FloatRect contentsRect; |
1275 | scale.render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect); |
1276 | contents.render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect); |
1277 | // Scale trough defines its size querying slider and highlight. |
1278 | if (part == SliderHorizontalPart) |
1279 | contentsRect.setHeight(trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height())); |
1280 | else |
1281 | contentsRect.setWidth(trough.preferredSize().width() + std::max(slider.preferredSize().width(), highlight.preferredSize().width())); |
1282 | FloatRect troughRect = contentsRect; |
1283 | trough.render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect); |
1284 | if (isFocused(renderObject)) |
1285 | trough.renderFocus(paintInfo.context().platformContext()->cr(), troughRect); |
1286 | |
1287 | LayoutPoint thumbLocation; |
1288 | if (is<HTMLInputElement>(renderObject.node())) { |
1289 | auto& input = downcast<HTMLInputElement>(*renderObject.node()); |
1290 | if (auto* element = input.sliderThumbElement()) |
1291 | thumbLocation = element->renderBox()->location(); |
1292 | } |
1293 | |
1294 | if (part == SliderHorizontalPart) { |
1295 | if (renderObject.style().direction() == TextDirection::RTL) { |
1296 | contentsRect.move(thumbLocation.x(), 0); |
1297 | contentsRect.setWidth(contentsRect.width() - thumbLocation.x()); |
1298 | } else |
1299 | contentsRect.setWidth(thumbLocation.x()); |
1300 | } else |
1301 | contentsRect.setHeight(thumbLocation.y()); |
1302 | highlight.render(paintInfo.context().platformContext()->cr(), contentsRect); |
1303 | |
1304 | return false; |
1305 | } |
1306 | |
1307 | void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const |
1308 | { |
1309 | ControlPart part = style.appearance(); |
1310 | if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart) |
1311 | return; |
1312 | |
1313 | auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider)); |
1314 | sliderWidget.scale().setState(GTK_STATE_FLAG_NORMAL); |
1315 | sliderWidget.trough().setState(GTK_STATE_FLAG_NORMAL); |
1316 | |
1317 | IntSize preferredSize = sliderWidget.scale().preferredSize(); |
1318 | preferredSize = preferredSize.expandedTo(sliderWidget.contents().preferredSize()); |
1319 | preferredSize = preferredSize.expandedTo(sliderWidget.trough().preferredSize()); |
1320 | preferredSize = preferredSize.expandedTo(sliderWidget.slider().preferredSize()); |
1321 | if (part == SliderThumbHorizontalPart) { |
1322 | style.setWidth(Length(preferredSize.width(), Fixed)); |
1323 | style.setHeight(Length(preferredSize.height(), Fixed)); |
1324 | return; |
1325 | } |
1326 | ASSERT(part == SliderThumbVerticalPart); |
1327 | style.setWidth(Length(preferredSize.height(), Fixed)); |
1328 | style.setHeight(Length(preferredSize.width(), Fixed)); |
1329 | } |
1330 | |
1331 | bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1332 | { |
1333 | ControlPart part = renderObject.style().appearance(); |
1334 | ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart); |
1335 | |
1336 | auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider)); |
1337 | auto scaleState = themePartStateFlags(*this, Scale, renderObject); |
1338 | auto& scale = sliderWidget.scale(); |
1339 | scale.setState(scaleState); |
1340 | auto& contents = sliderWidget.contents(); |
1341 | auto& trough = sliderWidget.trough(); |
1342 | trough.setState(scaleState); |
1343 | auto& slider = sliderWidget.slider(); |
1344 | slider.setState(themePartStateFlags(*this, ScaleSlider, renderObject)); |
1345 | auto& highlight = sliderWidget.highlight(); |
1346 | |
1347 | GtkBorder scaleContentsBox = scale.contentsBox(); |
1348 | GtkBorder contentsContentsBox = contents.contentsBox(); |
1349 | GtkBorder troughContentsBox = trough.contentsBox(); |
1350 | GtkBorder padding; |
1351 | padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left; |
1352 | padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right; |
1353 | padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top; |
1354 | padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom; |
1355 | |
1356 | // Scale trough defines its size querying slider and highlight. |
1357 | int troughHeight = trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height()); |
1358 | IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight)); |
1359 | sliderRect.move(padding.left, padding.top); |
1360 | sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom); |
1361 | slider.render(paintInfo.context().platformContext()->cr(), sliderRect); |
1362 | return false; |
1363 | } |
1364 | #else |
1365 | bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1366 | { |
1367 | ControlPart part = renderObject.style().appearance(); |
1368 | ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart); |
1369 | |
1370 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale); |
1371 | gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL); |
1372 | GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get()); |
1373 | gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction())); |
1374 | |
1375 | if (!isEnabled(renderObject)) |
1376 | gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE); |
1377 | |
1378 | IntRect sliderRect = rect; |
1379 | // GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and |
1380 | // the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes |
1381 | // and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look |
1382 | // good in other themes as well. |
1383 | static const int sliderSize = 4; |
1384 | |
1385 | if (part == SliderHorizontalPart) { |
1386 | sliderRect.setHeight(std::min(rect.height(), sliderSize)); |
1387 | sliderRect.move(0, (rect.height() - sliderRect.height()) / 2); |
1388 | } else { |
1389 | sliderRect.setWidth(std::min(rect.width(), sliderSize)); |
1390 | sliderRect.move((rect.width() - sliderRect.width()) / 2, 0); |
1391 | } |
1392 | |
1393 | gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height()); |
1394 | gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height()); |
1395 | |
1396 | if (isFocused(renderObject)) { |
1397 | gint focusWidth, focusPad; |
1398 | gtk_style_context_get_style(context.get(), "focus-line-width" , &focusWidth, "focus-padding" , &focusPad, nullptr); |
1399 | IntRect focusRect(sliderRect); |
1400 | focusRect.inflate(focusWidth + focusPad); |
1401 | gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height()); |
1402 | } |
1403 | |
1404 | return false; |
1405 | } |
1406 | |
1407 | void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const |
1408 | { |
1409 | ControlPart part = style.appearance(); |
1410 | if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart) |
1411 | return; |
1412 | |
1413 | GRefPtr<GtkStyleContext> context = createStyleContext(Scale); |
1414 | gint sliderWidth, sliderLength; |
1415 | gtk_style_context_get_style(context.get(), "slider-width" , &sliderWidth, "slider-length" , &sliderLength, nullptr); |
1416 | |
1417 | if (part == SliderThumbHorizontalPart) { |
1418 | style.setWidth(Length(sliderLength, Fixed)); |
1419 | style.setHeight(Length(sliderWidth, Fixed)); |
1420 | return; |
1421 | } |
1422 | ASSERT(part == SliderThumbVerticalPart); |
1423 | style.setWidth(Length(sliderWidth, Fixed)); |
1424 | style.setHeight(Length(sliderLength, Fixed)); |
1425 | } |
1426 | |
1427 | bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1428 | { |
1429 | ControlPart part = renderObject.style().appearance(); |
1430 | ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart); |
1431 | |
1432 | // FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle. |
1433 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale); |
1434 | gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL); |
1435 | GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get()); |
1436 | GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get()); |
1437 | gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction())); |
1438 | |
1439 | guint flags = 0; |
1440 | if (!isEnabled(renderObject)) |
1441 | flags |= GTK_STATE_FLAG_INSENSITIVE; |
1442 | else if (isHovered(renderObject)) |
1443 | flags |= GTK_STATE_FLAG_PRELIGHT; |
1444 | if (isPressed(renderObject)) |
1445 | flags |= GTK_STATE_FLAG_ACTIVE; |
1446 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags)); |
1447 | |
1448 | gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(), |
1449 | part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); |
1450 | |
1451 | return false; |
1452 | } |
1453 | #endif |
1454 | |
1455 | #if GTK_CHECK_VERSION(3, 20, 0) |
1456 | IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const |
1457 | { |
1458 | const auto& renderProgress = downcast<RenderProgress>(renderObject); |
1459 | auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar)); |
1460 | IntSize preferredSize = progressBarWidget.progressBar().preferredSize(); |
1461 | preferredSize = preferredSize.expandedTo(progressBarWidget.trough().preferredSize()); |
1462 | preferredSize = preferredSize.expandedTo(progressBarWidget.progress().preferredSize()); |
1463 | return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height()); |
1464 | } |
1465 | |
1466 | bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1467 | { |
1468 | if (!renderObject.isProgress()) |
1469 | return true; |
1470 | |
1471 | const auto& renderProgress = downcast<RenderProgress>(renderObject); |
1472 | auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar)); |
1473 | progressBarWidget.progressBar().render(paintInfo.context().platformContext()->cr(), rect); |
1474 | progressBarWidget.trough().render(paintInfo.context().platformContext()->cr(), rect); |
1475 | progressBarWidget.progress().render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect)); |
1476 | return false; |
1477 | } |
1478 | #else |
1479 | IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const |
1480 | { |
1481 | return bounds; |
1482 | } |
1483 | |
1484 | bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1485 | { |
1486 | if (!renderObject.isProgress()) |
1487 | return true; |
1488 | |
1489 | GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar); |
1490 | GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get()); |
1491 | GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get()); |
1492 | |
1493 | gtk_render_background(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
1494 | gtk_render_frame(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height()); |
1495 | |
1496 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0)); |
1497 | |
1498 | GtkBorder padding; |
1499 | gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding); |
1500 | IntRect progressRect( |
1501 | rect.x() + padding.left, |
1502 | rect.y() + padding.top, |
1503 | rect.width() - (padding.left + padding.right), |
1504 | rect.height() - (padding.top + padding.bottom)); |
1505 | progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect); |
1506 | |
1507 | if (!progressRect.isEmpty()) { |
1508 | #if GTK_CHECK_VERSION(3, 13, 7) |
1509 | gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height()); |
1510 | gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height()); |
1511 | #else |
1512 | gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height()); |
1513 | #endif |
1514 | } |
1515 | |
1516 | return false; |
1517 | } |
1518 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
1519 | |
1520 | #if GTK_CHECK_VERSION(3, 20, 0) |
1521 | RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const |
1522 | { |
1523 | return renderObject.style().direction() == TextDirection::RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight; |
1524 | } |
1525 | |
1526 | void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1527 | { |
1528 | style.setWidth(Length(spinButtonSize().width(), Fixed)); |
1529 | style.setHeight(Length(spinButtonSize().height(), Fixed)); |
1530 | } |
1531 | |
1532 | bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1533 | { |
1534 | auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton)); |
1535 | auto spinButtonState = themePartStateFlags(*this, SpinButton, renderObject); |
1536 | spinButtonWidget.spinButton().setState(spinButtonState); |
1537 | spinButtonWidget.entry().setState(spinButtonState); |
1538 | auto& up = spinButtonWidget.up(); |
1539 | up.setState(themePartStateFlags(*this, SpinButtonUpButton, renderObject)); |
1540 | auto& down = spinButtonWidget.down(); |
1541 | down.setState(themePartStateFlags(*this, SpinButtonDownButton, renderObject)); |
1542 | |
1543 | IntRect iconRect = rect; |
1544 | iconRect.setWidth(iconRect.width() / 2); |
1545 | if (renderObject.style().direction() == TextDirection::RTL) |
1546 | up.render(paintInfo.context().platformContext()->cr(), iconRect); |
1547 | else |
1548 | down.render(paintInfo.context().platformContext()->cr(), iconRect); |
1549 | iconRect.move(iconRect.width(), 0); |
1550 | if (renderObject.style().direction() == TextDirection::RTL) |
1551 | down.render(paintInfo.context().platformContext()->cr(), iconRect); |
1552 | else |
1553 | up.render(paintInfo.context().platformContext()->cr(), iconRect); |
1554 | |
1555 | return false; |
1556 | } |
1557 | #else |
1558 | RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const |
1559 | { |
1560 | return InnerSpinButtonLayout::Vertical; |
1561 | } |
1562 | static gint spinButtonArrowSize(GtkStyleContext* context) |
1563 | { |
1564 | PangoFontDescription* fontDescription; |
1565 | gtk_style_context_get(context, gtk_style_context_get_state(context), "font" , &fontDescription, nullptr); |
1566 | gint fontSize = pango_font_description_get_size(fontDescription); |
1567 | gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize); |
1568 | pango_font_description_free(fontDescription); |
1569 | |
1570 | return arrowSize - arrowSize % 2; // Force even. |
1571 | } |
1572 | |
1573 | void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1574 | { |
1575 | GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton); |
1576 | |
1577 | GtkBorder padding; |
1578 | gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding); |
1579 | |
1580 | int width = spinButtonArrowSize(context.get()) + padding.left + padding.right; |
1581 | style.setWidth(Length(width, Fixed)); |
1582 | style.setMinWidth(Length(width, Fixed)); |
1583 | } |
1584 | |
1585 | static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType) |
1586 | { |
1587 | ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN); |
1588 | |
1589 | GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext); |
1590 | GtkTextDirection direction = gtk_style_context_get_direction(context.get()); |
1591 | guint state = static_cast<guint>(gtk_style_context_get_state(context.get())); |
1592 | if (!(state & GTK_STATE_FLAG_INSENSITIVE)) { |
1593 | if (theme->isPressed(renderObject)) { |
1594 | if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject)) |
1595 | || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject))) |
1596 | state |= GTK_STATE_FLAG_ACTIVE; |
1597 | } else if (theme->isHovered(renderObject)) { |
1598 | if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject)) |
1599 | || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject))) |
1600 | state |= GTK_STATE_FLAG_PRELIGHT; |
1601 | } |
1602 | } |
1603 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state)); |
1604 | |
1605 | // Paint button. |
1606 | IntRect buttonRect(rect); |
1607 | guint junction = gtk_style_context_get_junction_sides(context.get()); |
1608 | if (arrowType == GTK_ARROW_UP) |
1609 | junction |= GTK_JUNCTION_BOTTOM; |
1610 | else { |
1611 | junction |= GTK_JUNCTION_TOP; |
1612 | buttonRect.move(0, rect.height() / 2); |
1613 | } |
1614 | buttonRect.setHeight(rect.height() / 2); |
1615 | gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction)); |
1616 | |
1617 | gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height()); |
1618 | gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height()); |
1619 | |
1620 | // Paint arrow centered inside button. |
1621 | // This code is based on gtkspinbutton.c code. |
1622 | IntRect arrowRect; |
1623 | gdouble angle; |
1624 | if (arrowType == GTK_ARROW_UP) { |
1625 | angle = 0; |
1626 | arrowRect.setY(rect.y()); |
1627 | arrowRect.setHeight(rect.height() / 2 - 2); |
1628 | } else { |
1629 | angle = G_PI; |
1630 | arrowRect.setY(rect.y() + buttonRect.y()); |
1631 | arrowRect.setHeight(rect.height() - arrowRect.y() - 2); |
1632 | } |
1633 | arrowRect.setWidth(rect.width() - 3); |
1634 | if (direction == GTK_TEXT_DIR_LTR) |
1635 | arrowRect.setX(rect.x() + 1); |
1636 | else |
1637 | arrowRect.setX(rect.x() + 2); |
1638 | |
1639 | gint width = arrowRect.width() / 2; |
1640 | width -= width % 2 - 1; // Force odd. |
1641 | gint height = (width + 1) / 2; |
1642 | |
1643 | arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2); |
1644 | gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width); |
1645 | } |
1646 | |
1647 | bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1648 | { |
1649 | GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton); |
1650 | gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction())); |
1651 | |
1652 | guint flags = 0; |
1653 | if (!isEnabled(renderObject) || isReadOnlyControl(renderObject)) |
1654 | flags |= GTK_STATE_FLAG_INSENSITIVE; |
1655 | else if (isFocused(renderObject)) |
1656 | flags |= GTK_STATE_FLAG_FOCUSED; |
1657 | gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags)); |
1658 | |
1659 | paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP); |
1660 | paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN); |
1661 | |
1662 | return false; |
1663 | } |
1664 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
1665 | |
1666 | Seconds RenderThemeGtk::caretBlinkInterval() const |
1667 | { |
1668 | GtkSettings* settings = gtk_settings_get_default(); |
1669 | |
1670 | gboolean shouldBlink; |
1671 | gint time; |
1672 | |
1673 | g_object_get(settings, "gtk-cursor-blink" , &shouldBlink, "gtk-cursor-blink-time" , &time, nullptr); |
1674 | |
1675 | if (!shouldBlink) |
1676 | return 0_s; |
1677 | |
1678 | return 500_us * time; |
1679 | } |
1680 | |
1681 | enum StyleColorType { StyleColorBackground, StyleColorForeground }; |
1682 | |
1683 | #if GTK_CHECK_VERSION(3, 20, 0) |
1684 | static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType) |
1685 | { |
1686 | RenderThemeGadget* gadget = nullptr; |
1687 | switch (themePart) { |
1688 | default: |
1689 | ASSERT_NOT_REACHED(); |
1690 | FALLTHROUGH; |
1691 | case Entry: |
1692 | gadget = &static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)).entry(); |
1693 | break; |
1694 | case EntrySelection: |
1695 | gadget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SelectedEntry)).selection(); |
1696 | break; |
1697 | case ListBox: |
1698 | gadget = &static_cast<RenderThemeListView&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ListView)).treeview(); |
1699 | break; |
1700 | case Button: |
1701 | gadget = &static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Button)).button(); |
1702 | break; |
1703 | case Window: |
1704 | gadget = &static_cast<RenderThemeWindow&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Window)).window(); |
1705 | break; |
1706 | } |
1707 | |
1708 | ASSERT(gadget); |
1709 | gadget->setState(state); |
1710 | return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color(); |
1711 | } |
1712 | #else |
1713 | static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType) |
1714 | { |
1715 | GRefPtr<GtkStyleContext> context = createStyleContext(themePart); |
1716 | gtk_style_context_set_state(context.get(), state); |
1717 | |
1718 | GdkRGBA gdkRGBAColor; |
1719 | if (colorType == StyleColorBackground) |
1720 | gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor); |
1721 | else |
1722 | gtk_style_context_get_color(context.get(), state, &gdkRGBAColor); |
1723 | return gdkRGBAColor; |
1724 | } |
1725 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
1726 | |
1727 | Color RenderThemeGtk::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const |
1728 | { |
1729 | return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground); |
1730 | } |
1731 | |
1732 | Color RenderThemeGtk::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const |
1733 | { |
1734 | return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorBackground); |
1735 | } |
1736 | |
1737 | Color RenderThemeGtk::platformActiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const |
1738 | { |
1739 | return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground); |
1740 | } |
1741 | |
1742 | Color RenderThemeGtk::platformInactiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const |
1743 | { |
1744 | return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorForeground); |
1745 | } |
1746 | |
1747 | Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const |
1748 | { |
1749 | return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground); |
1750 | } |
1751 | |
1752 | Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const |
1753 | { |
1754 | return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground); |
1755 | } |
1756 | |
1757 | Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const |
1758 | { |
1759 | return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground); |
1760 | } |
1761 | |
1762 | Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const |
1763 | { |
1764 | return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground); |
1765 | } |
1766 | |
1767 | Color RenderThemeGtk::disabledTextColor(const Color&, const Color&) const |
1768 | { |
1769 | return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground); |
1770 | } |
1771 | |
1772 | Color RenderThemeGtk::systemColor(CSSValueID cssValueId, OptionSet<StyleColor::Options> options) const |
1773 | { |
1774 | switch (cssValueId) { |
1775 | case CSSValueButtontext: |
1776 | return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground); |
1777 | case CSSValueCaptiontext: |
1778 | return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground); |
1779 | case CSSValueText: |
1780 | return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground); |
1781 | case CSSValueGraytext: |
1782 | return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground); |
1783 | case CSSValueWebkitControlBackground: |
1784 | return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorBackground); |
1785 | #if GTK_CHECK_VERSION(3, 20, 0) |
1786 | case CSSValueWindow: { |
1787 | // Only get window color from the theme in dark mode. |
1788 | gboolean preferDarkTheme = FALSE; |
1789 | if (auto* settings = gtk_settings_get_default()) |
1790 | g_object_get(settings, "gtk-application-prefer-dark-theme" , &preferDarkTheme, nullptr); |
1791 | if (preferDarkTheme) |
1792 | return styleColor(Window, GTK_STATE_FLAG_ACTIVE, StyleColorBackground); |
1793 | break; |
1794 | } |
1795 | #endif |
1796 | default: |
1797 | break; |
1798 | } |
1799 | |
1800 | return RenderTheme::systemColor(cssValueId, options); |
1801 | } |
1802 | |
1803 | void RenderThemeGtk::platformColorsDidChange() |
1804 | { |
1805 | RenderTheme::platformColorsDidChange(); |
1806 | } |
1807 | |
1808 | #if ENABLE(VIDEO) |
1809 | String RenderThemeGtk::() |
1810 | { |
1811 | return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet)); |
1812 | } |
1813 | |
1814 | #if ENABLE(FULLSCREEN_API) |
1815 | String RenderThemeGtk::() |
1816 | { |
1817 | return String(); |
1818 | } |
1819 | #endif |
1820 | |
1821 | #if GTK_CHECK_VERSION(3, 20, 0) |
1822 | bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName) |
1823 | { |
1824 | auto& iconWidget = static_cast<RenderThemeIcon&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Icon)); |
1825 | auto& icon = static_cast<RenderThemeIconGadget&>(iconWidget.icon()); |
1826 | icon.setState(themePartStateFlags(*this, MediaButton, renderObject)); |
1827 | icon.setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu); |
1828 | icon.setIconName(iconName); |
1829 | return !icon.render(graphicsContext.platformContext()->cr(), rect); |
1830 | } |
1831 | #else |
1832 | bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName) |
1833 | { |
1834 | GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton); |
1835 | gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction())); |
1836 | gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject)); |
1837 | static const unsigned mediaIconSize = 16; |
1838 | IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize); |
1839 | return !paintIcon(context.get(), graphicsContext, iconRect, iconName); |
1840 | } |
1841 | #endif // GTK_CHECK_VERSION(3, 20, 0) |
1842 | |
1843 | bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const |
1844 | { |
1845 | return (part != MediaMuteButtonPart); |
1846 | } |
1847 | |
1848 | bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1849 | { |
1850 | return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic" ); |
1851 | } |
1852 | |
1853 | bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1854 | { |
1855 | Node* node = renderObject.node(); |
1856 | if (!node) |
1857 | return true; |
1858 | Node* mediaNode = node->shadowHost(); |
1859 | if (!is<HTMLMediaElement>(mediaNode)) |
1860 | return true; |
1861 | |
1862 | HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode); |
1863 | return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic" ); |
1864 | } |
1865 | |
1866 | bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1867 | { |
1868 | Node* node = renderObject.node(); |
1869 | if (!node) |
1870 | return true; |
1871 | if (!nodeHasPseudo(*node, "-webkit-media-controls-play-button" )) |
1872 | return true; |
1873 | |
1874 | return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused" ) ? "media-playback-start-symbolic" : "media-playback-pause-symbolic" ); |
1875 | } |
1876 | |
1877 | bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1878 | { |
1879 | return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic" ); |
1880 | } |
1881 | |
1882 | bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1883 | { |
1884 | return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic" ); |
1885 | } |
1886 | |
1887 | #if ENABLE(VIDEO_TRACK) |
1888 | bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1889 | { |
1890 | return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic" ); |
1891 | } |
1892 | #endif |
1893 | |
1894 | static FloatRoundedRect::Radii borderRadiiFromStyle(const RenderStyle& style) |
1895 | { |
1896 | return FloatRoundedRect::Radii( |
1897 | IntSize(style.borderTopLeftRadius().width.intValue(), style.borderTopLeftRadius().height.intValue()), |
1898 | IntSize(style.borderTopRightRadius().width.intValue(), style.borderTopRightRadius().height.intValue()), |
1899 | IntSize(style.borderBottomLeftRadius().width.intValue(), style.borderBottomLeftRadius().height.intValue()), |
1900 | IntSize(style.borderBottomRightRadius().width.intValue(), style.borderBottomRightRadius().height.intValue())); |
1901 | } |
1902 | |
1903 | bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r) |
1904 | { |
1905 | auto mediaElement = parentMediaElement(o); |
1906 | if (!mediaElement) |
1907 | return true; |
1908 | |
1909 | GraphicsContext& context = paintInfo.context(); |
1910 | context.save(); |
1911 | context.setStrokeStyle(NoStroke); |
1912 | |
1913 | float mediaDuration = mediaElement->duration(); |
1914 | float totalTrackWidth = r.width(); |
1915 | auto& style = o.style(); |
1916 | RefPtr<TimeRanges> timeRanges = mediaElement->buffered(); |
1917 | for (unsigned index = 0; index < timeRanges->length(); ++index) { |
1918 | float start = timeRanges->start(index).releaseReturnValue(); |
1919 | float end = timeRanges->end(index).releaseReturnValue(); |
1920 | float startRatio = start / mediaDuration; |
1921 | float lengthRatio = (end - start) / mediaDuration; |
1922 | if (!lengthRatio) |
1923 | continue; |
1924 | |
1925 | IntRect rangeRect(r); |
1926 | rangeRect.setWidth(lengthRatio * totalTrackWidth); |
1927 | if (index) |
1928 | rangeRect.move(startRatio * totalTrackWidth, 0); |
1929 | context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor)); |
1930 | } |
1931 | |
1932 | context.restore(); |
1933 | return false; |
1934 | } |
1935 | |
1936 | bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r) |
1937 | { |
1938 | auto& style = o.style(); |
1939 | paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor)); |
1940 | return false; |
1941 | } |
1942 | |
1943 | bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1944 | { |
1945 | auto mediaElement = parentMediaElement(renderObject); |
1946 | if (!mediaElement) |
1947 | return true; |
1948 | |
1949 | float volume = mediaElement->muted() ? 0.0f : mediaElement->volume(); |
1950 | if (!volume) |
1951 | return true; |
1952 | |
1953 | GraphicsContext& context = paintInfo.context(); |
1954 | context.save(); |
1955 | context.setStrokeStyle(NoStroke); |
1956 | |
1957 | int rectHeight = rect.height(); |
1958 | float trackHeight = rectHeight * volume; |
1959 | auto& style = renderObject.style(); |
1960 | IntRect volumeRect(rect); |
1961 | volumeRect.move(0, rectHeight - trackHeight); |
1962 | volumeRect.setHeight(ceil(trackHeight)); |
1963 | |
1964 | context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor)); |
1965 | context.restore(); |
1966 | |
1967 | return false; |
1968 | } |
1969 | |
1970 | bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) |
1971 | { |
1972 | return paintMediaSliderThumb(renderObject, paintInfo, rect); |
1973 | } |
1974 | |
1975 | String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const |
1976 | { |
1977 | return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration); |
1978 | } |
1979 | |
1980 | bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&) |
1981 | { |
1982 | return false; |
1983 | } |
1984 | #endif |
1985 | |
1986 | void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, const Element*) const |
1987 | { |
1988 | style.setBoxShadow(nullptr); |
1989 | } |
1990 | |
1991 | // These values have been copied from RenderThemeChromiumSkia.cpp |
1992 | static const int progressActivityBlocks = 5; |
1993 | static const int progressAnimationFrames = 10; |
1994 | static const Seconds progressAnimationInterval { 125_ms }; |
1995 | Seconds RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const |
1996 | { |
1997 | return progressAnimationInterval; |
1998 | } |
1999 | |
2000 | Seconds RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const |
2001 | { |
2002 | return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth; |
2003 | } |
2004 | |
2005 | IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect) |
2006 | { |
2007 | IntRect progressRect(fullBarRect); |
2008 | const auto& renderProgress = downcast<RenderProgress>(renderObject); |
2009 | if (renderProgress.isDeterminate()) { |
2010 | int progressWidth = progressRect.width() * renderProgress.position(); |
2011 | if (renderObject.style().direction() == TextDirection::RTL) |
2012 | progressRect.setX(progressRect.x() + progressRect.width() - progressWidth); |
2013 | progressRect.setWidth(progressWidth); |
2014 | return progressRect; |
2015 | } |
2016 | |
2017 | double animationProgress = renderProgress.animationProgress(); |
2018 | |
2019 | // Never let the progress rect shrink smaller than 2 pixels. |
2020 | int newWidth = std::max(2, progressRect.width() / progressActivityBlocks); |
2021 | int movableWidth = progressRect.width() - newWidth; |
2022 | progressRect.setWidth(newWidth); |
2023 | |
2024 | // We want the first 0.5 units of the animation progress to represent the |
2025 | // forward motion and the second 0.5 units to represent the backward motion, |
2026 | // thus we multiply by two here to get the full sweep of the progress bar with |
2027 | // each direction. |
2028 | if (animationProgress < 0.5) |
2029 | progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth)); |
2030 | else |
2031 | progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth)); |
2032 | return progressRect; |
2033 | } |
2034 | |
2035 | String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const |
2036 | { |
2037 | if (width <= 0) |
2038 | return String(); |
2039 | |
2040 | if (fileList->length() > 1) |
2041 | return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font); |
2042 | |
2043 | String string; |
2044 | if (fileList->length()) |
2045 | string = FileSystem::pathGetFileName(fileList->item(0)->path()); |
2046 | else if (multipleFilesAllowed) |
2047 | string = fileButtonNoFilesSelectedLabel(); |
2048 | else |
2049 | string = fileButtonNoFileSelectedLabel(); |
2050 | |
2051 | return StringTruncator::centerTruncate(string, width, font); |
2052 | } |
2053 | |
2054 | #if ENABLE(VIDEO) |
2055 | String RenderThemeGtk::mediaControlsScript() |
2056 | { |
2057 | StringBuilder scriptBuilder; |
2058 | scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript)); |
2059 | scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript)); |
2060 | scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript)); |
2061 | return scriptBuilder.toString(); |
2062 | } |
2063 | #endif // ENABLE(VIDEO) |
2064 | |
2065 | #endif // GTK_API_VERSION_2 |
2066 | } |
2067 | |