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.
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.
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.
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.
26 #include "RenderThemeGtk.h"
28 #include "CSSValueKeywords.h"
30 #include "FileSystem.h"
31 #include "FloatRoundedRect.h"
32 #include "FontDescription.h"
33 #include "GRefPtrGtk.h"
34 #include "GUniquePtrGtk.h"
36 #include "GraphicsContext.h"
37 #include "GtkVersioning.h"
38 #include "HTMLInputElement.h"
39 #include "HTMLMediaElement.h"
40 #include "LocalizedStrings.h"
41 #include "MediaControlElements.h"
43 #include "PaintInfo.h"
44 #include "PlatformContextCairo.h"
45 #include "RenderBox.h"
46 #include "RenderObject.h"
47 #include "RenderProgress.h"
48 #include "RenderThemeWidget.h"
49 #include "ScrollbarThemeGtk.h"
50 #include "StringTruncator.h"
51 #include "TimeRanges.h"
52 #include "UserAgentScripts.h"
53 #include "UserAgentStyleSheets.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>
65 RenderTheme& RenderTheme::singleton()
67 static NeverDestroyed<RenderThemeGtk> theme;
71 static double getScreenDPI()
73 // FIXME: Really this should be the widget's screen.
74 GdkScreen* screen = gdk_screen_get_default();
76 return 96; // Default to 96 DPI.
78 float dpi = gdk_screen_get_resolution(screen);
84 void RenderThemeGtk::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const
86 GtkSettings* settings = gtk_settings_get_default();
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])
96 PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
97 if (!pangoDescription)
100 fontDescription.setOneFamily(pango_font_description_get_family(pangoDescription));
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);
107 fontDescription.setSpecifiedSize(size);
108 fontDescription.setIsAbsoluteSize(true);
109 fontDescription.setWeight(normalWeightValue());
110 fontDescription.setItalic(FontSelectionValue());
111 pango_font_description_free(pangoDescription);
114 #if ENABLE(DATALIST_ELEMENT)
115 IntSize RenderThemeGtk::sliderTickSize() const
117 // FIXME: We need to set this to the size of one tick mark.
118 return IntSize(0, 0);
121 int RenderThemeGtk::sliderTickOffsetFromTrackCenter() const
123 // FIXME: We need to set this to the position of the tick marks.
128 #ifndef GTK_API_VERSION_2
130 static void themeChangedCallback()
132 Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
135 RenderThemeGtk::RenderThemeGtk()
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;
146 enum RenderThemePart {
166 SpinButtonDownButton,
172 #if !GTK_CHECK_VERSION(3, 20, 0)
173 // This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c.
174 static const int minArrowSize = 15;
175 // This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
176 static const int minSpinButtonArrowSize = 6;
178 static GRefPtr<GtkStyleContext> createStyleContext(RenderThemePart themePart, GtkStyleContext* parent = nullptr)
180 GRefPtr<GtkWidgetPath> path = adoptGRef(parent ? gtk_widget_path_copy(gtk_style_context_get_path(parent)) : gtk_widget_path_new());
185 gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
186 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_ENTRY);
190 gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
193 gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
194 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
195 gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
198 gtk_widget_path_append_type(path.get(), GTK_TYPE_CHECK_BUTTON);
199 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_CHECK);
202 gtk_widget_path_append_type(path.get(), GTK_TYPE_RADIO_BUTTON);
203 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_RADIO);
206 gtk_widget_path_append_type(path.get(), GTK_TYPE_COMBO_BOX);
209 gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
210 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
211 gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
212 gtk_widget_path_iter_add_class(path.get(), -1, "combo");
215 gtk_widget_path_append_type(path.get(), GTK_TYPE_ARROW);
216 gtk_widget_path_iter_add_class(path.get(), -1, "arrow");
219 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
220 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
223 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
224 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
225 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
228 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
229 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
230 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SLIDER);
233 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
234 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
235 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
237 case ProgressBarTrough:
238 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
239 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
241 case ProgressBarProgress:
242 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
243 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
246 gtk_widget_path_append_type(path.get(), GTK_TYPE_TREE_VIEW);
247 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_VIEW);
250 gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
251 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
252 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
254 case SpinButtonUpButton:
255 case SpinButtonDownButton:
256 gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
257 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
258 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
262 gtk_widget_path_append_type(path.get(), GTK_TYPE_IMAGE);
263 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_IMAGE);
265 #endif // ENABLE(VIDEO)
267 ASSERT_NOT_REACHED();
271 GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
272 gtk_style_context_set_path(context.get(), path.get());
273 gtk_style_context_set_parent(context.get(), parent);
277 static GRefPtr<GdkPixbuf> loadThemedIcon(GtkStyleContext* context, const char* iconName, GtkIconSize iconSize)
279 GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(iconName));
280 unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
281 #if GTK_CHECK_VERSION(3, 14, 0)
282 GtkTextDirection direction = gtk_style_context_get_direction(context);
283 if (direction & GTK_TEXT_DIR_LTR)
284 lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
285 else if (direction & GTK_TEXT_DIR_RTL)
286 lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
289 gtk_icon_size_lookup(iconSize, &width, &height);
290 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 return adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), context, nullptr, nullptr));
296 #endif // !GTK_CHECK_VERSION(3, 20, 0)
298 static bool nodeHasPseudo(Node& node, const char* pseudo)
300 return is<Element>(node) && downcast<Element>(node).pseudo() == pseudo;
303 static bool nodeHasClass(Node* node, const char* className)
305 if (!is<Element>(*node))
308 Element& element = downcast<Element>(*node);
310 if (!element.hasClass())
313 return element.classNames().contains(className);
316 RenderThemeGtk::~RenderThemeGtk() = default;
318 static bool supportsFocus(ControlPart appearance)
320 switch (appearance) {
325 case SearchFieldPart:
329 case SliderHorizontalPart:
330 case SliderVerticalPart:
337 bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const
339 return supportsFocus(style.appearance());
342 bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const
347 int RenderThemeGtk::baselinePosition(const RenderBox& box) const
349 // FIXME: This strategy is possibly incorrect for the GTK+ port.
350 if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
351 return box.marginTop() + box.height() - 2;
352 return RenderTheme::baselinePosition(box);
355 #if GTK_CHECK_VERSION(3, 20, 0)
356 void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&)
359 static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject)
361 unsigned stateFlags = 0;
362 switch (renderObject.style().direction()) {
364 stateFlags |= GTK_STATE_FLAG_DIR_RTL;
367 stateFlags |= GTK_STATE_FLAG_DIR_LTR;
371 if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject)))
372 stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
374 if (theme.isHovered(renderObject))
375 stateFlags |= GTK_STATE_FLAG_PRELIGHT;
376 if (theme.isFocused(renderObject))
377 stateFlags |= GTK_STATE_FLAG_FOCUSED;
383 if (theme.isChecked(renderObject))
384 stateFlags |= GTK_STATE_FLAG_CHECKED;
385 if (theme.isIndeterminate(renderObject))
386 stateFlags |= GTK_STATE_FLAG_INCONSISTENT;
387 if (theme.isPressed(renderObject))
388 stateFlags |= GTK_STATE_FLAG_SELECTED;
398 if (theme.isPressed(renderObject))
399 stateFlags |= GTK_STATE_FLAG_ACTIVE;
401 case SpinButtonUpButton:
402 if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject))
403 stateFlags |= GTK_STATE_FLAG_ACTIVE;
404 if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject))
405 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
407 case SpinButtonDownButton:
408 if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject))
409 stateFlags |= GTK_STATE_FLAG_ACTIVE;
410 if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject))
411 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
417 return static_cast<GtkStateFlags>(stateFlags);
420 static GtkTextDirection gtkTextDirection(TextDirection direction)
424 return GTK_TEXT_DIR_RTL;
426 return GTK_TEXT_DIR_LTR;
428 return GTK_TEXT_DIR_NONE;
432 static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject)
434 if (!theme->isEnabled(renderObject))
435 return GTK_STATE_FLAG_INSENSITIVE;
436 if (theme->isPressed(renderObject))
437 return GTK_STATE_FLAG_ACTIVE;
438 if (theme->isHovered(renderObject))
439 return GTK_STATE_FLAG_PRELIGHT;
441 return GTK_STATE_FLAG_NORMAL;
444 static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
446 gint focusWidth, focusPad;
447 gtk_style_context_get_style(context, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
448 rect.inflate(focusWidth + focusPad);
451 void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
453 GRefPtr<GtkStyleContext> context;
454 bool checkInteriorFocus = false;
455 ControlPart part = renderObject.style().appearance();
459 context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton);
461 gint indicatorSpacing;
462 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
463 rect.inflate(indicatorSpacing);
466 case SliderVerticalPart:
467 case SliderHorizontalPart:
468 context = createStyleContext(ScaleSlider);
471 case MenulistButtonPart:
473 context = createStyleContext(Button);
474 checkInteriorFocus = true;
478 context = createStyleContext(Entry);
479 checkInteriorFocus = true;
486 if (checkInteriorFocus) {
487 gboolean interiorFocus;
488 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, nullptr);
492 adjustRectForFocus(context.get(), rect);
494 #endif // GTK_CHECK_VERSION(3, 20, 0)
496 void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
498 // Some layout tests check explicitly that buttons ignore line-height.
499 if (style.appearance() == PushButtonPart)
500 style.setLineHeight(RenderStyle::initialLineHeight());
503 static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize)
505 if (rect.width() > minSize.width()) {
506 rect.inflateX(-(rect.width() - minSize.width()) / 2);
507 rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1.
510 if (rect.height() > minSize.height()) {
511 rect.inflateY(-(rect.height() - minSize.height()) / 2);
512 rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1.
516 #if GTK_CHECK_VERSION(3, 20, 0)
517 static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
519 ASSERT(themePart == CheckButton || themePart == RadioButton);
521 // The width and height are both specified, so we shouldn't change them.
522 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
525 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
526 toggleWidget.button().setState(GTK_STATE_FLAG_NORMAL);
527 toggleWidget.toggle().setState(GTK_STATE_FLAG_NORMAL);
528 IntSize preferredSize = toggleWidget.button().preferredSize();
529 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
531 if (style.width().isIntrinsicOrAuto())
532 style.setWidth(Length(preferredSize.width(), Fixed));
534 if (style.height().isAuto())
535 style.setHeight(Length(preferredSize.height(), Fixed));
538 static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
540 ASSERT(themePart == CheckButton || themePart == RadioButton);
542 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
543 auto toggleState = themePartStateFlags(*theme, themePart, renderObject);
544 toggleWidget.button().setState(toggleState);
545 toggleWidget.toggle().setState(toggleState);
547 FloatRect rect = fullRect;
548 // Some themes do not render large toggle buttons properly, so we simply
549 // shrink the rectangle back down to the default size and then center it
550 // in the full toggle button region. The reason for not simply forcing toggle
551 // buttons to be a smaller size is that we don't want to break site layouts.
552 IntSize preferredSize = toggleWidget.button().preferredSize();
553 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
554 shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize);
555 toggleWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
556 toggleWidget.toggle().render(paintInfo.context().platformContext()->cr(), rect);
558 if (theme->isFocused(renderObject))
559 toggleWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
562 static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
564 // The width and height are both specified, so we shouldn't change them.
565 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
568 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
569 // Other ports hard-code this to 13. GTK+ users tend to demand the native look.
571 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
573 if (style.width().isIntrinsicOrAuto())
574 style.setWidth(Length(indicatorSize, Fixed));
576 if (style.height().isAuto())
577 style.setHeight(Length(indicatorSize, Fixed));
580 static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
582 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
583 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
586 if (!theme->isEnabled(renderObject))
587 flags |= GTK_STATE_FLAG_INSENSITIVE;
588 else if (theme->isHovered(renderObject))
589 flags |= GTK_STATE_FLAG_PRELIGHT;
590 if (theme->isIndeterminate(renderObject))
591 flags |= GTK_STATE_FLAG_INCONSISTENT;
592 else if (theme->isChecked(renderObject))
593 #if GTK_CHECK_VERSION(3, 13, 7)
594 flags |= GTK_STATE_FLAG_CHECKED;
596 flags |= GTK_STATE_FLAG_ACTIVE;
598 if (theme->isPressed(renderObject))
599 flags |= GTK_STATE_FLAG_SELECTED;
600 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
602 // Some themes do not render large toggle buttons properly, so we simply
603 // shrink the rectangle back down to the default size and then center it
604 // in the full toggle button region. The reason for not simply forcing toggle
605 // buttons to be a smaller size is that we don't want to break site layouts.
606 FloatRect rect(fullRect);
608 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
609 IntSize minSize(indicatorSize, indicatorSize);
610 shrinkToMinimumSizeAndCenterRectangle(rect, minSize);
612 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
613 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
615 if (themePart == CheckButton)
616 gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
618 gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
620 if (theme->isFocused(renderObject)) {
621 IntRect indicatorRect(rect);
622 gint indicatorSpacing;
623 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
624 indicatorRect.inflate(indicatorSpacing);
625 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
626 indicatorRect.width(), indicatorRect.height());
629 #endif // GTK_CHECK_VERSION(3, 20, 0)
631 void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
633 setToggleSize(CheckButton, style);
636 bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
638 paintToggle(this, CheckButton, renderObject, paintInfo, rect);
642 void RenderThemeGtk::setRadioSize(RenderStyle& style) const
644 setToggleSize(RadioButton, style);
647 bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
649 paintToggle(this, RadioButton, renderObject, paintInfo, rect);
653 #if GTK_CHECK_VERSION(3, 20, 0)
654 bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
656 auto& buttonWidget = static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(isDefault(renderObject) ? RenderThemeWidget::Type::ButtonDefault : RenderThemeWidget::Type::Button));
657 buttonWidget.button().setState(themePartStateFlags(*this, Button, renderObject));
658 buttonWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
659 if (isFocused(renderObject))
660 buttonWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
664 static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
666 IntRect buttonRect(rect);
669 if (!theme->isEnabled(renderObject))
670 flags |= GTK_STATE_FLAG_INSENSITIVE;
671 else if (theme->isHovered(renderObject))
672 flags |= GTK_STATE_FLAG_PRELIGHT;
673 if (theme->isPressed(renderObject))
674 flags |= GTK_STATE_FLAG_ACTIVE;
675 gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
677 if (theme->isDefault(renderObject)) {
678 GtkBorder* borderPtr = 0;
679 GtkBorder border = { 1, 1, 1, 1 };
681 gtk_style_context_get_style(context, "default-border", &borderPtr, nullptr);
684 gtk_border_free(borderPtr);
687 buttonRect.move(border.left, border.top);
688 buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
689 buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));
691 gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
694 gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
695 gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
697 if (theme->isFocused(renderObject)) {
698 gint focusWidth, focusPad;
699 gboolean displaceFocus, interiorFocus;
700 gtk_style_context_get_style(
702 "focus-line-width", &focusWidth,
703 "focus-padding", &focusPad,
704 "interior-focus", &interiorFocus,
705 "displace-focus", &displaceFocus,
709 GtkBorder borderWidth;
710 gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth);
712 buttonRect = IntRect(
713 buttonRect.x() + borderWidth.left + focusPad,
714 buttonRect.y() + borderWidth.top + focusPad,
715 buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
716 buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
718 buttonRect.inflate(focusWidth + focusPad);
720 if (displaceFocus && theme->isPressed(renderObject)) {
721 gint childDisplacementX;
722 gint childDisplacementY;
723 gtk_style_context_get_style(context, "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
724 buttonRect.move(childDisplacementX, childDisplacementY);
727 gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
730 bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
732 GRefPtr<GtkStyleContext> context = createStyleContext(Button);
733 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
734 renderButton(this, context.get(), renderObject, paintInfo, rect);
737 #endif // GTK_CHECK_VERSION(3, 20, 0)
739 static Color menuListColor(const Element* element)
741 #if GTK_CHECK_VERSION(3, 20, 0)
742 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
743 GtkStateFlags state = element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
744 comboWidget.comboBox().setState(state);
745 comboWidget.button().setState(state);
746 return comboWidget.button().color();
748 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
749 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
750 gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL);
752 GdkRGBA gdkRGBAColor;
753 gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor);
755 #endif // GTK_CHECK_VERSION(3, 20, 0)
758 void RenderThemeGtk::adjustMenuListStyle(StyleResolver&, RenderStyle& style, const Element* element) const
760 // The tests check explicitly that select menu buttons ignore line height.
761 style.setLineHeight(RenderStyle::initialLineHeight());
763 // We cannot give a proper rendering when border radius is active, unfortunately.
764 style.resetBorderRadius();
767 style.setColor(menuListColor(element));
770 void RenderThemeGtk::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
772 adjustMenuListStyle(styleResolver, style, e);
775 #if GTK_CHECK_VERSION(3, 20, 0)
777 * GtkComboBox gadgets tree
780 * ├── box.linked
781 * │ ╰── button.combo
783 * │ ├── cellview
784 * │ ╰── arrow
785 * ╰── window.popup
787 LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
789 if (style.appearance() == NoControlPart)
792 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
793 comboWidget.comboBox().setState(GTK_STATE_FLAG_NORMAL);
794 comboWidget.button().setState(GTK_STATE_FLAG_NORMAL);
795 comboWidget.arrow().setState(GTK_STATE_FLAG_NORMAL);
796 GtkBorder comboContentsBox = comboWidget.comboBox().contentsBox();
797 GtkBorder boxContentsBox = comboWidget.box().contentsBox();
798 GtkBorder buttonContentsBox = comboWidget.button().contentsBox();
799 GtkBorder buttonBoxContentsBox = comboWidget.buttonBox().contentsBox();
801 padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left;
802 padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right;
803 padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top;
804 padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom;
806 auto arrowSize = comboWidget.arrow().preferredSize();
807 return LengthBox(padding.top, padding.right + (style.direction() == LTR ? arrowSize.width() : 0),
808 padding.bottom, padding.left + (style.direction() == RTL ? arrowSize.width() : 0));
811 bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
813 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
814 auto comboState = themePartStateFlags(*this, ComboBoxButton, renderObject);
815 comboWidget.comboBox().setState(comboState);
816 comboWidget.button().setState(comboState);
817 comboWidget.arrow().setState(comboState);
819 cairo_t* cr = paintInfo.context().platformContext()->cr();
820 comboWidget.comboBox().render(cr, rect);
821 comboWidget.box().render(cr, rect);
822 FloatRect contentsRect;
823 comboWidget.button().render(cr, rect, &contentsRect);
824 comboWidget.buttonBox().render(cr, contentsRect);
825 comboWidget.arrow().render(cr, contentsRect);
826 if (isFocused(renderObject))
827 comboWidget.button().renderFocus(cr, rect);
832 LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
834 if (style.appearance() == NoControlPart)
835 return { 0, 0, 0, 0 };
837 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox);
838 GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get());
839 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
840 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
841 GtkBorder borderWidth = { 0, 0, 0, 0 };
842 gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth);
844 gboolean interiorFocus;
845 gint focusWidth, focusPad;
846 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
847 focusWidth = interiorFocus ? focusWidth + focusPad : 0;
849 return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == LTR ? minArrowSize : 0),
850 borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == RTL ? minArrowSize : 0) };
853 bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
855 // FIXME: adopt subpixel themes.
856 IntRect rect = IntRect(r);
858 cairo_t* cairoContext = paintInfo.context().platformContext()->cr();
859 GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
861 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
864 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
865 gtk_style_context_set_direction(buttonStyleContext.get(), direction);
866 renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect);
868 // Get the inner rectangle.
869 gint focusWidth, focusPad;
870 GtkBorder* innerBorderPtr = 0;
871 GtkBorder innerBorder = { 1, 1, 1, 1 };
872 gtk_style_context_get_style(buttonStyleContext.get(), "inner-border", &innerBorderPtr, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
873 if (innerBorderPtr) {
874 innerBorder = *innerBorderPtr;
875 gtk_border_free(innerBorderPtr);
878 GtkBorder borderWidth;
879 GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get());
880 gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth);
882 focusWidth += focusPad;
884 rect.x() + innerBorder.left + borderWidth.left + focusWidth,
885 rect.y() + innerBorder.top + borderWidth.top + focusWidth,
886 rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
887 rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));
889 if (isPressed(renderObject)) {
890 gint childDisplacementX;
891 gint childDisplacementY;
892 gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
893 innerRect.move(childDisplacementX, childDisplacementY);
895 innerRect.setWidth(std::max(1, innerRect.width()));
896 innerRect.setHeight(std::max(1, innerRect.height()));
899 GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get());
900 gtk_style_context_set_direction(arrowStyleContext.get(), direction);
903 gtk_style_context_get_style(parentStyleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
905 IntSize arrowSize(minArrowSize, innerRect.height());
906 FloatPoint arrowPosition(innerRect.location());
907 if (direction == GTK_TEXT_DIR_LTR)
908 arrowPosition.move(innerRect.width() - arrowSize.width(), 0);
910 // GTK+ actually fetches the xalign and valign values from the widget, but since we
911 // don't have a widget here, we are just using the default xalign and valign values of 0.5.
912 gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
913 arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);
915 gtk_style_context_set_state(arrowStyleContext.get(), state);
916 gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);
920 #endif // GTK_CHECK_VERSION(3, 20, 0)
922 bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect)
924 return paintMenuList(object, info, rect);
927 bool RenderThemeGtk::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
929 // To avoid rendering issues with dark themes, if text input elements have color styling, we don't style them with GTK.
930 if ((style.appearance() == TextFieldPart || style.appearance() == TextAreaPart || style.appearance() == SearchFieldPart) && style.color() != RenderStyle::initialColor())
933 return RenderTheme::isControlStyled(style, border, background, backgroundColor);
936 #if GTK_CHECK_VERSION(3, 20, 0)
938 static IntSize spinButtonSize()
940 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
941 spinButtonWidget.spinButton().setState(GTK_STATE_FLAG_NORMAL);
942 spinButtonWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
943 spinButtonWidget.up().setState(GTK_STATE_FLAG_NORMAL);
944 spinButtonWidget.down().setState(GTK_STATE_FLAG_NORMAL);
946 IntSize preferredSize = spinButtonWidget.spinButton().preferredSize();
947 preferredSize = preferredSize.expandedTo(spinButtonWidget.entry().preferredSize());
948 IntSize upPreferredSize = preferredSize.expandedTo(spinButtonWidget.up().preferredSize());
949 IntSize downPreferredSize = preferredSize.expandedTo(spinButtonWidget.down().preferredSize());
951 return IntSize(upPreferredSize.width() + downPreferredSize.width(), std::max(upPreferredSize.height(), downPreferredSize.height()));
955 void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, const Element* element) const
957 if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element)))
960 style.setMinHeight(Length(spinButtonSize().height(), Fixed));
962 // The default theme for the GTK+ port uses very wide spin buttons (66px) compared to what other
963 // browsers use (~13 px). And unfortunately, most of the web developers won't test how their site
964 // renders on WebKitGTK+. To ensure that spin buttons don't end up covering the values of the input
965 // field, we override the width of the input element and always increment it with the width needed
966 // for the spinbutton (when drawing the spinbutton).
967 int minimumWidth = style.width().intValue() + spinButtonSize().width();
968 style.setMinWidth(Length(minimumWidth, Fixed));
971 bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
973 if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) {
974 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
975 auto spinButtonState = themePartStateFlags(*this, Entry, renderObject);
976 spinButtonWidget.spinButton().setState(spinButtonState);
977 spinButtonWidget.entry().setState(spinButtonState);
978 spinButtonWidget.spinButton().render(paintInfo.context().platformContext()->cr(), rect);
979 spinButtonWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
981 auto& entryWidget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry));
982 entryWidget.entry().setState(themePartStateFlags(*this, Entry, renderObject));
983 entryWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
988 void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const
992 bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
994 GRefPtr<GtkStyleContext> context = createStyleContext(Entry);
995 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
998 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
999 flags |= GTK_STATE_FLAG_INSENSITIVE;
1000 else if (isFocused(renderObject))
1001 flags |= GTK_STATE_FLAG_FOCUSED;
1002 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1004 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1005 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1007 if (isFocused(renderObject) && isEnabled(renderObject)) {
1008 gboolean interiorFocus;
1009 gint focusWidth, focusPad;
1010 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1011 if (!interiorFocus) {
1012 IntRect focusRect(rect);
1013 focusRect.inflate(focusWidth + focusPad);
1014 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1022 #if GTK_CHECK_VERSION(3, 20, 0)
1023 static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1025 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1026 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1027 searchEntryWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
1028 searchEntryWidget.leftIcon().setState(GTK_STATE_FLAG_NORMAL);
1029 searchEntryWidget.rightIcon().setState(GTK_STATE_FLAG_NORMAL);
1031 // Get the icon size based on the font size.
1032 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1033 icon.setIconSize(style.computedFontPixelSize());
1034 IntSize preferredSize = icon.preferredSize();
1035 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1036 if (themePart == EntryIconLeft)
1037 preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom);
1039 preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom);
1040 style.setWidth(Length(preferredSize.width(), Fixed));
1041 style.setHeight(Length(preferredSize.height(), Fixed));
1044 // Defined in GTK+ (gtk/gtkiconfactory.c)
1045 static const gint gtkIconSizeMenu = 16;
1046 static const gint gtkIconSizeSmallToolbar = 18;
1047 static const gint gtkIconSizeButton = 20;
1048 static const gint gtkIconSizeLargeToolbar = 24;
1049 static const gint gtkIconSizeDnd = 32;
1050 static const gint gtkIconSizeDialog = 48;
1052 static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
1054 if (pixelSize < gtkIconSizeSmallToolbar)
1055 return GTK_ICON_SIZE_MENU;
1056 if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
1057 return GTK_ICON_SIZE_SMALL_TOOLBAR;
1058 if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
1059 return GTK_ICON_SIZE_BUTTON;
1060 if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
1061 return GTK_ICON_SIZE_LARGE_TOOLBAR;
1062 if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
1063 return GTK_ICON_SIZE_DND;
1065 return GTK_ICON_SIZE_DIALOG;
1068 static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1070 style.resetBorder();
1071 style.resetPadding();
1073 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1074 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1077 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1079 // Get the icon size based on the font size.
1080 int fontSize = style.computedFontPixelSize();
1081 if (fontSize < gtkIconSizeMenu) {
1082 style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed));
1083 style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed));
1086 gint width = 0, height = 0;
1087 gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
1088 style.setWidth(Length(width + (padding.left + padding.right), Fixed));
1089 style.setHeight(Length(height + (padding.top + padding.bottom), Fixed));
1093 bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r)
1095 return paintTextField(o, i, r);
1098 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
1100 adjustSearchFieldCancelButtonStyle(styleResolver, style, e);
1103 bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect)
1105 return paintSearchFieldResultsDecorationPart(o, i, rect);
1108 void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
1110 adjustSearchFieldIconStyle(EntryIconLeft, style);
1113 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1115 adjustSearchFieldIconStyle(EntryIconRight, style);
1118 #if GTK_CHECK_VERSION(3, 20, 0)
1119 static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1121 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1122 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1123 searchEntryWidget.entry().setState(themePartStateFlags(*theme, Entry, renderObject));
1124 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1125 icon.setState(themePartStateFlags(*theme, themePart, renderObject));
1126 icon.setIconSize(renderObject.style().computedFontPixelSize());
1127 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1128 IntRect iconRect = rect;
1129 if (themePart == EntryIconLeft) {
1130 iconRect.move(contentsBox.left, contentsBox.top);
1131 iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom);
1133 iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom);
1134 return !icon.render(paintInfo.context().platformContext()->cr(), iconRect);
1136 bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1138 return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect);
1141 bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1143 return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect);
1146 static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1148 GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height()));
1152 if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height())
1153 icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR));
1155 gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y());
1159 static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state)
1161 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1162 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1163 gtk_style_context_set_direction(context.get(), direction);
1164 gtk_style_context_set_state(context.get(), state);
1165 return paintIcon(context.get(), graphicsContext, rect, iconName);
1168 static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect)
1170 if (!renderObject.node())
1173 // Get the renderer of <input> element.
1174 Node* input = renderObject.node()->shadowHost();
1176 input = renderObject.node();
1177 if (!is<RenderBox>(*input->renderer()))
1180 // If possible center the y-coordinate of the rect vertically in the parent input element.
1181 // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
1182 // that are even, which looks in relation to the box text.
1183 IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox();
1185 // Make sure the scaled decoration stays square and will fit in its parent's box.
1186 int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
1187 IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
1191 bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1193 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1194 if (iconRect.isEmpty())
1197 return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1198 gtkIconStateFlags(this, renderObject));
1201 bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1203 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1204 if (iconRect.isEmpty())
1207 return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1208 gtkIconStateFlags(this, renderObject));
1210 #endif // GTK_CHECK_VERSION(3, 20, 0)
1212 void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, const Element*) const
1214 // We cannot give a proper rendering when border radius is active, unfortunately.
1215 style.resetBorderRadius();
1216 style.setLineHeight(RenderStyle::initialLineHeight());
1219 bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect)
1221 return paintTextField(o, i, rect);
1224 bool RenderThemeGtk::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const
1226 return element.isPasswordField();
1229 void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const
1231 style.setBoxShadow(nullptr);
1234 void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const
1236 RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
1237 style.setBoxShadow(nullptr);
1240 #if GTK_CHECK_VERSION(3, 20, 0)
1245 * ╰── contents
1248 * ╰── [highlight]
1250 bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1252 ControlPart part = renderObject.style().appearance();
1253 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1255 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1256 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1257 auto& scale = sliderWidget.scale();
1258 scale.setState(scaleState);
1259 auto& contents = sliderWidget.contents();
1260 auto& trough = sliderWidget.trough();
1261 trough.setState(scaleState);
1262 auto& slider = sliderWidget.slider();
1263 auto& highlight = sliderWidget.highlight();
1265 // The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it.
1266 IntSize preferredSize = scale.preferredSize();
1267 preferredSize = preferredSize.expandedTo(contents.preferredSize());
1268 preferredSize = preferredSize.expandedTo(trough.preferredSize());
1269 FloatRect trackRect = rect;
1270 if (part == SliderHorizontalPart) {
1271 trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2));
1272 trackRect.setHeight(preferredSize.height());
1274 trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0);
1275 trackRect.setWidth(preferredSize.width());
1278 FloatRect contentsRect;
1279 scale.render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect);
1280 contents.render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect);
1281 // Scale trough defines its size querying slider and highlight.
1282 if (part == SliderHorizontalPart)
1283 contentsRect.setHeight(trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height()));
1285 contentsRect.setWidth(trough.preferredSize().width() + std::max(slider.preferredSize().width(), highlight.preferredSize().width()));
1286 FloatRect troughRect = contentsRect;
1287 trough.render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect);
1288 if (isFocused(renderObject))
1289 trough.renderFocus(paintInfo.context().platformContext()->cr(), troughRect);
1291 LayoutPoint thumbLocation;
1292 if (is<HTMLInputElement>(renderObject.node())) {
1293 auto& input = downcast<HTMLInputElement>(*renderObject.node());
1294 if (auto* element = input.sliderThumbElement())
1295 thumbLocation = element->renderBox()->location();
1298 if (part == SliderHorizontalPart) {
1299 if (renderObject.style().direction() == RTL) {
1300 contentsRect.move(thumbLocation.x(), 0);
1301 contentsRect.setWidth(contentsRect.width() - thumbLocation.x());
1303 contentsRect.setWidth(thumbLocation.x());
1305 contentsRect.setHeight(thumbLocation.y());
1306 highlight.render(paintInfo.context().platformContext()->cr(), contentsRect);
1311 void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1313 ControlPart part = style.appearance();
1314 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1317 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1318 sliderWidget.scale().setState(GTK_STATE_FLAG_NORMAL);
1319 sliderWidget.trough().setState(GTK_STATE_FLAG_NORMAL);
1321 IntSize preferredSize = sliderWidget.scale().preferredSize();
1322 preferredSize = preferredSize.expandedTo(sliderWidget.contents().preferredSize());
1323 preferredSize = preferredSize.expandedTo(sliderWidget.trough().preferredSize());
1324 preferredSize = preferredSize.expandedTo(sliderWidget.slider().preferredSize());
1325 if (part == SliderThumbHorizontalPart) {
1326 style.setWidth(Length(preferredSize.width(), Fixed));
1327 style.setHeight(Length(preferredSize.height(), Fixed));
1330 ASSERT(part == SliderThumbVerticalPart);
1331 style.setWidth(Length(preferredSize.height(), Fixed));
1332 style.setHeight(Length(preferredSize.width(), Fixed));
1335 bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1337 ControlPart part = renderObject.style().appearance();
1338 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1340 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1341 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1342 auto& scale = sliderWidget.scale();
1343 scale.setState(scaleState);
1344 auto& contents = sliderWidget.contents();
1345 auto& trough = sliderWidget.trough();
1346 trough.setState(scaleState);
1347 auto& slider = sliderWidget.slider();
1348 slider.setState(themePartStateFlags(*this, ScaleSlider, renderObject));
1349 auto& highlight = sliderWidget.highlight();
1351 GtkBorder scaleContentsBox = scale.contentsBox();
1352 GtkBorder contentsContentsBox = contents.contentsBox();
1353 GtkBorder troughContentsBox = trough.contentsBox();
1355 padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left;
1356 padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right;
1357 padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top;
1358 padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom;
1360 // Scale trough defines its size querying slider and highlight.
1361 int troughHeight = trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height());
1362 IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight));
1363 sliderRect.move(padding.left, padding.top);
1364 sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom);
1365 slider.render(paintInfo.context().platformContext()->cr(), sliderRect);
1369 bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1371 ControlPart part = renderObject.style().appearance();
1372 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1374 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1375 gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1376 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get());
1377 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1379 if (!isEnabled(renderObject))
1380 gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE);
1382 IntRect sliderRect = rect;
1383 // GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and
1384 // the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes
1385 // and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look
1386 // good in other themes as well.
1387 static const int sliderSize = 4;
1389 if (part == SliderHorizontalPart) {
1390 sliderRect.setHeight(std::min(rect.height(), sliderSize));
1391 sliderRect.move(0, (rect.height() - sliderRect.height()) / 2);
1393 sliderRect.setWidth(std::min(rect.width(), sliderSize));
1394 sliderRect.move((rect.width() - sliderRect.width()) / 2, 0);
1397 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1398 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1400 if (isFocused(renderObject)) {
1401 gint focusWidth, focusPad;
1402 gtk_style_context_get_style(context.get(), "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1403 IntRect focusRect(sliderRect);
1404 focusRect.inflate(focusWidth + focusPad);
1405 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1411 void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1413 ControlPart part = style.appearance();
1414 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1417 GRefPtr<GtkStyleContext> context = createStyleContext(Scale);
1418 gint sliderWidth, sliderLength;
1419 gtk_style_context_get_style(context.get(), "slider-width", &sliderWidth, "slider-length", &sliderLength, nullptr);
1421 if (part == SliderThumbHorizontalPart) {
1422 style.setWidth(Length(sliderLength, Fixed));
1423 style.setHeight(Length(sliderWidth, Fixed));
1426 ASSERT(part == SliderThumbVerticalPart);
1427 style.setWidth(Length(sliderWidth, Fixed));
1428 style.setHeight(Length(sliderLength, Fixed));
1431 bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1433 ControlPart part = renderObject.style().appearance();
1434 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1436 // FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle.
1437 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1438 gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1439 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get());
1440 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get());
1441 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1444 if (!isEnabled(renderObject))
1445 flags |= GTK_STATE_FLAG_INSENSITIVE;
1446 else if (isHovered(renderObject))
1447 flags |= GTK_STATE_FLAG_PRELIGHT;
1448 if (isPressed(renderObject))
1449 flags |= GTK_STATE_FLAG_ACTIVE;
1450 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1452 gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
1453 part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
1459 #if GTK_CHECK_VERSION(3, 20, 0)
1460 IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
1462 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1463 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1464 IntSize preferredSize = progressBarWidget.progressBar().preferredSize();
1465 preferredSize = preferredSize.expandedTo(progressBarWidget.trough().preferredSize());
1466 preferredSize = preferredSize.expandedTo(progressBarWidget.progress().preferredSize());
1467 return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height());
1470 bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1472 if (!renderObject.isProgress())
1475 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1476 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1477 progressBarWidget.progressBar().render(paintInfo.context().platformContext()->cr(), rect);
1478 progressBarWidget.trough().render(paintInfo.context().platformContext()->cr(), rect);
1479 progressBarWidget.progress().render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect));
1483 IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const
1488 bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1490 if (!renderObject.isProgress())
1493 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar);
1494 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get());
1495 GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get());
1497 gtk_render_background(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1498 gtk_render_frame(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1500 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
1503 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1504 IntRect progressRect(
1505 rect.x() + padding.left,
1506 rect.y() + padding.top,
1507 rect.width() - (padding.left + padding.right),
1508 rect.height() - (padding.top + padding.bottom));
1509 progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
1511 if (!progressRect.isEmpty()) {
1512 #if GTK_CHECK_VERSION(3, 13, 7)
1513 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1514 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1516 gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1522 #endif // GTK_CHECK_VERSION(3, 20, 0)
1524 #if GTK_CHECK_VERSION(3, 20, 0)
1525 RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const
1527 return renderObject.style().direction() == RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight;
1530 void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1532 style.setWidth(Length(spinButtonSize().width(), Fixed));
1533 style.setHeight(Length(spinButtonSize().height(), Fixed));
1536 bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1538 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
1539 auto spinButtonState = themePartStateFlags(*this, SpinButton, renderObject);
1540 spinButtonWidget.spinButton().setState(spinButtonState);
1541 spinButtonWidget.entry().setState(spinButtonState);
1542 auto& up = spinButtonWidget.up();
1543 up.setState(themePartStateFlags(*this, SpinButtonUpButton, renderObject));
1544 auto& down = spinButtonWidget.down();
1545 down.setState(themePartStateFlags(*this, SpinButtonDownButton, renderObject));
1547 IntRect iconRect = rect;
1548 iconRect.setWidth(iconRect.width() / 2);
1549 if (renderObject.style().direction() == RTL)
1550 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1552 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1553 iconRect.move(iconRect.width(), 0);
1554 if (renderObject.style().direction() == RTL)
1555 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1557 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1562 RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const
1564 return InnerSpinButtonLayout::Vertical;
1566 static gint spinButtonArrowSize(GtkStyleContext* context)
1568 PangoFontDescription* fontDescription;
1569 gtk_style_context_get(context, gtk_style_context_get_state(context), "font", &fontDescription, nullptr);
1570 gint fontSize = pango_font_description_get_size(fontDescription);
1571 gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
1572 pango_font_description_free(fontDescription);
1574 return arrowSize - arrowSize % 2; // Force even.
1577 void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1579 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1582 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1584 int width = spinButtonArrowSize(context.get()) + padding.left + padding.right;
1585 style.setWidth(Length(width, Fixed));
1586 style.setMinWidth(Length(width, Fixed));
1589 static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
1591 ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);
1593 GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext);
1594 GtkTextDirection direction = gtk_style_context_get_direction(context.get());
1595 guint state = static_cast<guint>(gtk_style_context_get_state(context.get()));
1596 if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
1597 if (theme->isPressed(renderObject)) {
1598 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
1599 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
1600 state |= GTK_STATE_FLAG_ACTIVE;
1601 } else if (theme->isHovered(renderObject)) {
1602 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
1603 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
1604 state |= GTK_STATE_FLAG_PRELIGHT;
1607 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state));
1610 IntRect buttonRect(rect);
1611 guint junction = gtk_style_context_get_junction_sides(context.get());
1612 if (arrowType == GTK_ARROW_UP)
1613 junction |= GTK_JUNCTION_BOTTOM;
1615 junction |= GTK_JUNCTION_TOP;
1616 buttonRect.move(0, rect.height() / 2);
1618 buttonRect.setHeight(rect.height() / 2);
1619 gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction));
1621 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1622 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1624 // Paint arrow centered inside button.
1625 // This code is based on gtkspinbutton.c code.
1628 if (arrowType == GTK_ARROW_UP) {
1630 arrowRect.setY(rect.y());
1631 arrowRect.setHeight(rect.height() / 2 - 2);
1634 arrowRect.setY(rect.y() + buttonRect.y());
1635 arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
1637 arrowRect.setWidth(rect.width() - 3);
1638 if (direction == GTK_TEXT_DIR_LTR)
1639 arrowRect.setX(rect.x() + 1);
1641 arrowRect.setX(rect.x() + 2);
1643 gint width = arrowRect.width() / 2;
1644 width -= width % 2 - 1; // Force odd.
1645 gint height = (width + 1) / 2;
1647 arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
1648 gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
1651 bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1653 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1654 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1657 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
1658 flags |= GTK_STATE_FLAG_INSENSITIVE;
1659 else if (isFocused(renderObject))
1660 flags |= GTK_STATE_FLAG_FOCUSED;
1661 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1663 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP);
1664 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN);
1668 #endif // GTK_CHECK_VERSION(3, 20, 0)
1670 Seconds RenderThemeGtk::caretBlinkInterval() const
1672 GtkSettings* settings = gtk_settings_get_default();
1674 gboolean shouldBlink;
1677 g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, nullptr);
1682 return 500_us * time;
1685 enum StyleColorType { StyleColorBackground, StyleColorForeground };
1687 #if GTK_CHECK_VERSION(3, 20, 0)
1688 static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1690 RenderThemeGadget* gadget = nullptr;
1691 switch (themePart) {
1693 ASSERT_NOT_REACHED();
1696 gadget = &static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)).entry();
1698 case EntrySelection:
1699 gadget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SelectedEntry)).selection();
1702 gadget = &static_cast<RenderThemeListView&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ListView)).treeview();
1705 gadget = &static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Button)).button();
1710 gadget->setState(state);
1711 return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color();
1714 static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1716 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
1717 gtk_style_context_set_state(context.get(), state);
1719 GdkRGBA gdkRGBAColor;
1720 if (colorType == StyleColorBackground)
1721 gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor);
1723 gtk_style_context_get_color(context.get(), state, &gdkRGBAColor);
1724 return gdkRGBAColor;
1726 #endif // GTK_CHECK_VERSION(3, 20, 0)
1728 Color RenderThemeGtk::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1730 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1733 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1735 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1738 Color RenderThemeGtk::platformActiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1740 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1743 Color RenderThemeGtk::platformInactiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1745 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1748 Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1750 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1753 Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1755 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1758 Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1760 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1763 Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1765 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1768 Color RenderThemeGtk::systemColor(CSSValueID cssValueId, OptionSet<StyleColor::Options> options) const
1770 switch (cssValueId) {
1771 case CSSValueButtontext:
1772 return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1773 case CSSValueCaptiontext:
1774 return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1776 return RenderTheme::systemColor(cssValueId, options);
1780 void RenderThemeGtk::platformColorsDidChange()
1782 RenderTheme::platformColorsDidChange();
1786 String RenderThemeGtk::extraMediaControlsStyleSheet()
1788 return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
1791 #if ENABLE(FULLSCREEN_API)
1792 String RenderThemeGtk::extraFullScreenStyleSheet()
1798 #if GTK_CHECK_VERSION(3, 20, 0)
1799 bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1801 auto& iconWidget = static_cast<RenderThemeIcon&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Icon));
1802 auto& icon = static_cast<RenderThemeIconGadget&>(iconWidget.icon());
1803 icon.setState(themePartStateFlags(*this, MediaButton, renderObject));
1804 icon.setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
1805 icon.setIconName(iconName);
1806 return !icon.render(graphicsContext.platformContext()->cr(), rect);
1809 bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1811 GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton);
1812 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1813 gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject));
1814 static const unsigned mediaIconSize = 16;
1815 IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize);
1816 return !paintIcon(context.get(), graphicsContext, iconRect, iconName);
1818 #endif // GTK_CHECK_VERSION(3, 20, 0)
1820 bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const
1822 return (part != MediaMuteButtonPart);
1825 bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1827 return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic");
1830 bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1832 Node* node = renderObject.node();
1835 Node* mediaNode = node->shadowHost();
1836 if (!is<HTMLMediaElement>(mediaNode))
1839 HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode);
1840 return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
1843 bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1845 Node* node = renderObject.node();
1848 if (!nodeHasPseudo(*node, "-webkit-media-controls-play-button"))
1851 return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused") ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
1854 bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1856 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic");
1859 bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1861 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic");
1864 #if ENABLE(VIDEO_TRACK)
1865 bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1867 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic");
1871 static FloatRoundedRect::Radii borderRadiiFromStyle(const RenderStyle& style)
1873 return FloatRoundedRect::Radii(
1874 IntSize(style.borderTopLeftRadius().width.intValue(), style.borderTopLeftRadius().height.intValue()),
1875 IntSize(style.borderTopRightRadius().width.intValue(), style.borderTopRightRadius().height.intValue()),
1876 IntSize(style.borderBottomLeftRadius().width.intValue(), style.borderBottomLeftRadius().height.intValue()),
1877 IntSize(style.borderBottomRightRadius().width.intValue(), style.borderBottomRightRadius().height.intValue()));
1880 bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1882 auto mediaElement = parentMediaElement(o);
1886 GraphicsContext& context = paintInfo.context();
1888 context.setStrokeStyle(NoStroke);
1890 float mediaDuration = mediaElement->duration();
1891 float totalTrackWidth = r.width();
1892 auto& style = o.style();
1893 RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
1894 for (unsigned index = 0; index < timeRanges->length(); ++index) {
1895 float start = timeRanges->start(index).releaseReturnValue();
1896 float end = timeRanges->end(index).releaseReturnValue();
1897 float startRatio = start / mediaDuration;
1898 float lengthRatio = (end - start) / mediaDuration;
1902 IntRect rangeRect(r);
1903 rangeRect.setWidth(lengthRatio * totalTrackWidth);
1905 rangeRect.move(startRatio * totalTrackWidth, 0);
1906 context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1913 bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1915 auto& style = o.style();
1916 paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1920 bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1922 auto mediaElement = parentMediaElement(renderObject);
1926 float volume = mediaElement->muted() ? 0.0f : mediaElement->volume();
1930 GraphicsContext& context = paintInfo.context();
1932 context.setStrokeStyle(NoStroke);
1934 int rectHeight = rect.height();
1935 float trackHeight = rectHeight * volume;
1936 auto& style = renderObject.style();
1937 IntRect volumeRect(rect);
1938 volumeRect.move(0, rectHeight - trackHeight);
1939 volumeRect.setHeight(ceil(trackHeight));
1941 context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1947 bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1949 return paintMediaSliderThumb(renderObject, paintInfo, rect);
1952 String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
1954 return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
1957 bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&)
1963 void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, const Element*) const
1965 style.setBoxShadow(nullptr);
1968 // These values have been copied from RenderThemeChromiumSkia.cpp
1969 static const int progressActivityBlocks = 5;
1970 static const int progressAnimationFrames = 10;
1971 static const Seconds progressAnimationInterval { 125_ms };
1972 Seconds RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const
1974 return progressAnimationInterval;
1977 Seconds RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const
1979 return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
1982 IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect)
1984 IntRect progressRect(fullBarRect);
1985 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1986 if (renderProgress.isDeterminate()) {
1987 int progressWidth = progressRect.width() * renderProgress.position();
1988 if (renderObject.style().direction() == RTL)
1989 progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
1990 progressRect.setWidth(progressWidth);
1991 return progressRect;
1994 double animationProgress = renderProgress.animationProgress();
1996 // Never let the progress rect shrink smaller than 2 pixels.
1997 int newWidth = std::max(2, progressRect.width() / progressActivityBlocks);
1998 int movableWidth = progressRect.width() - newWidth;
1999 progressRect.setWidth(newWidth);
2001 // We want the first 0.5 units of the animation progress to represent the
2002 // forward motion and the second 0.5 units to represent the backward motion,
2003 // thus we multiply by two here to get the full sweep of the progress bar with
2005 if (animationProgress < 0.5)
2006 progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
2008 progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));
2009 return progressRect;
2012 String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
2017 if (fileList->length() > 1)
2018 return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
2021 if (fileList->length())
2022 string = FileSystem::pathGetFileName(fileList->item(0)->path());
2023 else if (multipleFilesAllowed)
2024 string = fileButtonNoFilesSelectedLabel();
2026 string = fileButtonNoFileSelectedLabel();
2028 return StringTruncator::centerTruncate(string, width, font);
2032 String RenderThemeGtk::mediaControlsScript()
2034 StringBuilder scriptBuilder;
2035 scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
2036 scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript));
2037 scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript));
2038 return scriptBuilder.toString();
2040 #endif // ENABLE(VIDEO)
2042 #endif // GTK_API_VERSION_2