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 "FloatRoundedRect.h"
31 #include "FontDescription.h"
32 #include "GRefPtrGtk.h"
33 #include "GUniquePtrGtk.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"
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"
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>
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)
299 static bool nodeHasPseudo(Node& node, const char* pseudo)
301 return is<Element>(node) && downcast<Element>(node).pseudo() == pseudo;
304 static bool nodeHasClass(Node* node, const char* className)
306 if (!is<Element>(*node))
309 Element& element = downcast<Element>(*node);
311 if (!element.hasClass())
314 return element.classNames().contains(className);
316 #endif // ENABLE(VIDEO)
318 RenderThemeGtk::~RenderThemeGtk() = default;
320 static bool supportsFocus(ControlPart appearance)
322 switch (appearance) {
327 case SearchFieldPart:
331 case SliderHorizontalPart:
332 case SliderVerticalPart:
339 bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const
341 return supportsFocus(style.appearance());
344 bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const
349 int RenderThemeGtk::baselinePosition(const RenderBox& box) const
351 // FIXME: This strategy is possibly incorrect for the GTK+ port.
352 if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
353 return box.marginTop() + box.height() - 2;
354 return RenderTheme::baselinePosition(box);
357 #if GTK_CHECK_VERSION(3, 20, 0)
358 void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&)
361 static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject)
363 unsigned stateFlags = 0;
364 switch (renderObject.style().direction()) {
365 case TextDirection::RTL:
366 stateFlags |= GTK_STATE_FLAG_DIR_RTL;
368 case TextDirection::LTR:
369 stateFlags |= GTK_STATE_FLAG_DIR_LTR;
373 if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject)))
374 stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
376 if (theme.isHovered(renderObject))
377 stateFlags |= GTK_STATE_FLAG_PRELIGHT;
378 if (theme.isFocused(renderObject))
379 stateFlags |= GTK_STATE_FLAG_FOCUSED;
385 if (theme.isChecked(renderObject))
386 stateFlags |= GTK_STATE_FLAG_CHECKED;
387 if (theme.isIndeterminate(renderObject))
388 stateFlags |= GTK_STATE_FLAG_INCONSISTENT;
389 if (theme.isPressed(renderObject))
390 stateFlags |= GTK_STATE_FLAG_SELECTED;
400 if (theme.isPressed(renderObject))
401 stateFlags |= GTK_STATE_FLAG_ACTIVE;
403 case SpinButtonUpButton:
404 if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject))
405 stateFlags |= GTK_STATE_FLAG_ACTIVE;
406 if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject))
407 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
409 case SpinButtonDownButton:
410 if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject))
411 stateFlags |= GTK_STATE_FLAG_ACTIVE;
412 if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject))
413 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
419 return static_cast<GtkStateFlags>(stateFlags);
422 static GtkTextDirection gtkTextDirection(TextDirection direction)
425 case TextDirection::RTL:
426 return GTK_TEXT_DIR_RTL;
427 case TextDirection::LTR:
428 return GTK_TEXT_DIR_LTR;
430 return GTK_TEXT_DIR_NONE;
434 static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject)
436 if (!theme->isEnabled(renderObject))
437 return GTK_STATE_FLAG_INSENSITIVE;
438 if (theme->isPressed(renderObject))
439 return GTK_STATE_FLAG_ACTIVE;
440 if (theme->isHovered(renderObject))
441 return GTK_STATE_FLAG_PRELIGHT;
443 return GTK_STATE_FLAG_NORMAL;
446 static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
448 gint focusWidth, focusPad;
449 gtk_style_context_get_style(context, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
450 rect.inflate(focusWidth + focusPad);
453 void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
455 GRefPtr<GtkStyleContext> context;
456 bool checkInteriorFocus = false;
457 ControlPart part = renderObject.style().appearance();
461 context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton);
463 gint indicatorSpacing;
464 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
465 rect.inflate(indicatorSpacing);
468 case SliderVerticalPart:
469 case SliderHorizontalPart:
470 context = createStyleContext(ScaleSlider);
473 case MenulistButtonPart:
475 context = createStyleContext(Button);
476 checkInteriorFocus = true;
480 context = createStyleContext(Entry);
481 checkInteriorFocus = true;
488 if (checkInteriorFocus) {
489 gboolean interiorFocus;
490 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, nullptr);
494 adjustRectForFocus(context.get(), rect);
496 #endif // GTK_CHECK_VERSION(3, 20, 0)
498 void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
500 // Some layout tests check explicitly that buttons ignore line-height.
501 if (style.appearance() == PushButtonPart)
502 style.setLineHeight(RenderStyle::initialLineHeight());
505 static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize)
507 if (rect.width() > minSize.width()) {
508 rect.inflateX(-(rect.width() - minSize.width()) / 2);
509 rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1.
512 if (rect.height() > minSize.height()) {
513 rect.inflateY(-(rect.height() - minSize.height()) / 2);
514 rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1.
518 #if GTK_CHECK_VERSION(3, 20, 0)
519 static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
521 ASSERT(themePart == CheckButton || themePart == RadioButton);
523 // The width and height are both specified, so we shouldn't change them.
524 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
527 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
528 toggleWidget.button().setState(GTK_STATE_FLAG_NORMAL);
529 toggleWidget.toggle().setState(GTK_STATE_FLAG_NORMAL);
530 IntSize preferredSize = toggleWidget.button().preferredSize();
531 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
533 if (style.width().isIntrinsicOrAuto())
534 style.setWidth(Length(preferredSize.width(), Fixed));
536 if (style.height().isAuto())
537 style.setHeight(Length(preferredSize.height(), Fixed));
540 static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
542 ASSERT(themePart == CheckButton || themePart == RadioButton);
544 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
545 auto toggleState = themePartStateFlags(*theme, themePart, renderObject);
546 toggleWidget.button().setState(toggleState);
547 toggleWidget.toggle().setState(toggleState);
549 FloatRect rect = fullRect;
550 // Some themes do not render large toggle buttons properly, so we simply
551 // shrink the rectangle back down to the default size and then center it
552 // in the full toggle button region. The reason for not simply forcing toggle
553 // buttons to be a smaller size is that we don't want to break site layouts.
554 IntSize preferredSize = toggleWidget.button().preferredSize();
555 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
556 shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize);
557 toggleWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
558 toggleWidget.toggle().render(paintInfo.context().platformContext()->cr(), rect);
560 if (theme->isFocused(renderObject))
561 toggleWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
564 static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
566 // The width and height are both specified, so we shouldn't change them.
567 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
570 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
571 // Other ports hard-code this to 13. GTK+ users tend to demand the native look.
573 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
575 if (style.width().isIntrinsicOrAuto())
576 style.setWidth(Length(indicatorSize, Fixed));
578 if (style.height().isAuto())
579 style.setHeight(Length(indicatorSize, Fixed));
582 static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
584 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
585 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
588 if (!theme->isEnabled(renderObject))
589 flags |= GTK_STATE_FLAG_INSENSITIVE;
590 else if (theme->isHovered(renderObject))
591 flags |= GTK_STATE_FLAG_PRELIGHT;
592 if (theme->isIndeterminate(renderObject))
593 flags |= GTK_STATE_FLAG_INCONSISTENT;
594 else if (theme->isChecked(renderObject))
595 #if GTK_CHECK_VERSION(3, 13, 7)
596 flags |= GTK_STATE_FLAG_CHECKED;
598 flags |= GTK_STATE_FLAG_ACTIVE;
600 if (theme->isPressed(renderObject))
601 flags |= GTK_STATE_FLAG_SELECTED;
602 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
604 // Some themes do not render large toggle buttons properly, so we simply
605 // shrink the rectangle back down to the default size and then center it
606 // in the full toggle button region. The reason for not simply forcing toggle
607 // buttons to be a smaller size is that we don't want to break site layouts.
608 FloatRect rect(fullRect);
610 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
611 IntSize minSize(indicatorSize, indicatorSize);
612 shrinkToMinimumSizeAndCenterRectangle(rect, minSize);
614 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
615 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
617 if (themePart == CheckButton)
618 gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
620 gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
622 if (theme->isFocused(renderObject)) {
623 IntRect indicatorRect(rect);
624 gint indicatorSpacing;
625 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
626 indicatorRect.inflate(indicatorSpacing);
627 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
628 indicatorRect.width(), indicatorRect.height());
631 #endif // GTK_CHECK_VERSION(3, 20, 0)
633 void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
635 setToggleSize(CheckButton, style);
638 bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
640 paintToggle(this, CheckButton, renderObject, paintInfo, rect);
644 void RenderThemeGtk::setRadioSize(RenderStyle& style) const
646 setToggleSize(RadioButton, style);
649 bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
651 paintToggle(this, RadioButton, renderObject, paintInfo, rect);
655 #if GTK_CHECK_VERSION(3, 20, 0)
656 bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
658 auto& buttonWidget = static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(isDefault(renderObject) ? RenderThemeWidget::Type::ButtonDefault : RenderThemeWidget::Type::Button));
659 buttonWidget.button().setState(themePartStateFlags(*this, Button, renderObject));
660 buttonWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
661 if (isFocused(renderObject))
662 buttonWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
666 static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
668 IntRect buttonRect(rect);
671 if (!theme->isEnabled(renderObject))
672 flags |= GTK_STATE_FLAG_INSENSITIVE;
673 else if (theme->isHovered(renderObject))
674 flags |= GTK_STATE_FLAG_PRELIGHT;
675 if (theme->isPressed(renderObject))
676 flags |= GTK_STATE_FLAG_ACTIVE;
677 gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
679 if (theme->isDefault(renderObject)) {
680 GtkBorder* borderPtr = 0;
681 GtkBorder border = { 1, 1, 1, 1 };
683 gtk_style_context_get_style(context, "default-border", &borderPtr, nullptr);
686 gtk_border_free(borderPtr);
689 buttonRect.move(border.left, border.top);
690 buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
691 buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));
693 gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
696 gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
697 gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
699 if (theme->isFocused(renderObject)) {
700 gint focusWidth, focusPad;
701 gboolean displaceFocus, interiorFocus;
702 gtk_style_context_get_style(
704 "focus-line-width", &focusWidth,
705 "focus-padding", &focusPad,
706 "interior-focus", &interiorFocus,
707 "displace-focus", &displaceFocus,
711 GtkBorder borderWidth;
712 gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth);
714 buttonRect = IntRect(
715 buttonRect.x() + borderWidth.left + focusPad,
716 buttonRect.y() + borderWidth.top + focusPad,
717 buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
718 buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
720 buttonRect.inflate(focusWidth + focusPad);
722 if (displaceFocus && theme->isPressed(renderObject)) {
723 gint childDisplacementX;
724 gint childDisplacementY;
725 gtk_style_context_get_style(context, "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
726 buttonRect.move(childDisplacementX, childDisplacementY);
729 gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
732 bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
734 GRefPtr<GtkStyleContext> context = createStyleContext(Button);
735 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
736 renderButton(this, context.get(), renderObject, paintInfo, rect);
739 #endif // GTK_CHECK_VERSION(3, 20, 0)
741 static Color menuListColor(const Element* element)
743 #if GTK_CHECK_VERSION(3, 20, 0)
744 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
745 GtkStateFlags state = element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
746 comboWidget.comboBox().setState(state);
747 comboWidget.button().setState(state);
748 return comboWidget.button().color();
750 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
751 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
752 gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL);
754 GdkRGBA gdkRGBAColor;
755 gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor);
757 #endif // GTK_CHECK_VERSION(3, 20, 0)
760 void RenderThemeGtk::adjustMenuListStyle(StyleResolver&, RenderStyle& style, const Element* element) const
762 // The tests check explicitly that select menu buttons ignore line height.
763 style.setLineHeight(RenderStyle::initialLineHeight());
765 // We cannot give a proper rendering when border radius is active, unfortunately.
766 style.resetBorderRadius();
769 style.setColor(menuListColor(element));
772 void RenderThemeGtk::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
774 adjustMenuListStyle(styleResolver, style, e);
777 #if GTK_CHECK_VERSION(3, 20, 0)
779 * GtkComboBox gadgets tree
782 * ├── box.linked
783 * │ ╰── button.combo
785 * │ ├── cellview
786 * │ ╰── arrow
787 * ╰── window.popup
789 LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
791 if (style.appearance() == NoControlPart)
794 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
795 comboWidget.comboBox().setState(GTK_STATE_FLAG_NORMAL);
796 comboWidget.button().setState(GTK_STATE_FLAG_NORMAL);
797 comboWidget.arrow().setState(GTK_STATE_FLAG_NORMAL);
798 GtkBorder comboContentsBox = comboWidget.comboBox().contentsBox();
799 GtkBorder boxContentsBox = comboWidget.box().contentsBox();
800 GtkBorder buttonContentsBox = comboWidget.button().contentsBox();
801 GtkBorder buttonBoxContentsBox = comboWidget.buttonBox().contentsBox();
803 padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left;
804 padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right;
805 padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top;
806 padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom;
808 auto arrowSize = comboWidget.arrow().preferredSize();
809 return LengthBox(padding.top, padding.right + (style.direction() == TextDirection::LTR ? arrowSize.width() : 0),
810 padding.bottom, padding.left + (style.direction() == TextDirection::RTL ? arrowSize.width() : 0));
813 bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
815 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
816 auto comboState = themePartStateFlags(*this, ComboBoxButton, renderObject);
817 comboWidget.comboBox().setState(comboState);
818 comboWidget.button().setState(comboState);
819 comboWidget.arrow().setState(comboState);
821 cairo_t* cr = paintInfo.context().platformContext()->cr();
822 comboWidget.comboBox().render(cr, rect);
823 comboWidget.box().render(cr, rect);
824 FloatRect contentsRect;
825 comboWidget.button().render(cr, rect, &contentsRect);
826 comboWidget.buttonBox().render(cr, contentsRect);
827 comboWidget.arrow().render(cr, contentsRect);
828 if (isFocused(renderObject))
829 comboWidget.button().renderFocus(cr, rect);
834 LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
836 if (style.appearance() == NoControlPart)
837 return { 0, 0, 0, 0 };
839 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox);
840 GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get());
841 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
842 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
843 GtkBorder borderWidth = { 0, 0, 0, 0 };
844 gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth);
846 gboolean interiorFocus;
847 gint focusWidth, focusPad;
848 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
849 focusWidth = interiorFocus ? focusWidth + focusPad : 0;
851 return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == TextDirection::LTR ? minArrowSize : 0),
852 borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == TextDirection::RTL ? minArrowSize : 0) };
855 bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
857 // FIXME: adopt subpixel themes.
858 IntRect rect = IntRect(r);
860 cairo_t* cairoContext = paintInfo.context().platformContext()->cr();
861 GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
863 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
866 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
867 gtk_style_context_set_direction(buttonStyleContext.get(), direction);
868 renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect);
870 // Get the inner rectangle.
871 gint focusWidth, focusPad;
872 GtkBorder* innerBorderPtr = 0;
873 GtkBorder innerBorder = { 1, 1, 1, 1 };
874 gtk_style_context_get_style(buttonStyleContext.get(), "inner-border", &innerBorderPtr, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
875 if (innerBorderPtr) {
876 innerBorder = *innerBorderPtr;
877 gtk_border_free(innerBorderPtr);
880 GtkBorder borderWidth;
881 GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get());
882 gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth);
884 focusWidth += focusPad;
886 rect.x() + innerBorder.left + borderWidth.left + focusWidth,
887 rect.y() + innerBorder.top + borderWidth.top + focusWidth,
888 rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
889 rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));
891 if (isPressed(renderObject)) {
892 gint childDisplacementX;
893 gint childDisplacementY;
894 gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
895 innerRect.move(childDisplacementX, childDisplacementY);
897 innerRect.setWidth(std::max(1, innerRect.width()));
898 innerRect.setHeight(std::max(1, innerRect.height()));
901 GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get());
902 gtk_style_context_set_direction(arrowStyleContext.get(), direction);
905 gtk_style_context_get_style(parentStyleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
907 IntSize arrowSize(minArrowSize, innerRect.height());
908 FloatPoint arrowPosition(innerRect.location());
909 if (direction == GTK_TEXT_DIR_LTR)
910 arrowPosition.move(innerRect.width() - arrowSize.width(), 0);
912 // GTK+ actually fetches the xalign and valign values from the widget, but since we
913 // don't have a widget here, we are just using the default xalign and valign values of 0.5.
914 gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
915 arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);
917 gtk_style_context_set_state(arrowStyleContext.get(), state);
918 gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);
922 #endif // GTK_CHECK_VERSION(3, 20, 0)
924 bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect)
926 return paintMenuList(object, info, rect);
929 bool RenderThemeGtk::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
931 // To avoid rendering issues with dark themes, if text input elements have color styling, we don't style them with GTK.
932 if ((style.appearance() == TextFieldPart || style.appearance() == TextAreaPart || style.appearance() == SearchFieldPart) && style.color() != RenderStyle::initialColor())
935 return RenderTheme::isControlStyled(style, border, background, backgroundColor);
938 #if GTK_CHECK_VERSION(3, 20, 0)
940 static IntSize spinButtonSize()
942 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
943 spinButtonWidget.spinButton().setState(GTK_STATE_FLAG_NORMAL);
944 spinButtonWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
945 spinButtonWidget.up().setState(GTK_STATE_FLAG_NORMAL);
946 spinButtonWidget.down().setState(GTK_STATE_FLAG_NORMAL);
948 IntSize preferredSize = spinButtonWidget.spinButton().preferredSize();
949 preferredSize = preferredSize.expandedTo(spinButtonWidget.entry().preferredSize());
950 IntSize upPreferredSize = preferredSize.expandedTo(spinButtonWidget.up().preferredSize());
951 IntSize downPreferredSize = preferredSize.expandedTo(spinButtonWidget.down().preferredSize());
953 return IntSize(upPreferredSize.width() + downPreferredSize.width(), std::max(upPreferredSize.height(), downPreferredSize.height()));
957 void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, const Element* element) const
959 if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element)))
962 style.setMinHeight(Length(spinButtonSize().height(), Fixed));
964 // The default theme for the GTK+ port uses very wide spin buttons (66px) compared to what other
965 // browsers use (~13 px). And unfortunately, most of the web developers won't test how their site
966 // renders on WebKitGTK+. To ensure that spin buttons don't end up covering the values of the input
967 // field, we override the width of the input element and always increment it with the width needed
968 // for the spinbutton (when drawing the spinbutton).
969 int minimumWidth = style.width().intValue() + spinButtonSize().width();
970 style.setMinWidth(Length(minimumWidth, Fixed));
973 bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
975 if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) {
976 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
977 auto spinButtonState = themePartStateFlags(*this, Entry, renderObject);
978 spinButtonWidget.spinButton().setState(spinButtonState);
979 spinButtonWidget.entry().setState(spinButtonState);
980 spinButtonWidget.spinButton().render(paintInfo.context().platformContext()->cr(), rect);
981 spinButtonWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
983 auto& entryWidget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry));
984 entryWidget.entry().setState(themePartStateFlags(*this, Entry, renderObject));
985 entryWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
990 void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const
994 bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
996 GRefPtr<GtkStyleContext> context = createStyleContext(Entry);
997 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
1000 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
1001 flags |= GTK_STATE_FLAG_INSENSITIVE;
1002 else if (isFocused(renderObject))
1003 flags |= GTK_STATE_FLAG_FOCUSED;
1004 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1006 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1007 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1009 if (isFocused(renderObject) && isEnabled(renderObject)) {
1010 gboolean interiorFocus;
1011 gint focusWidth, focusPad;
1012 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1013 if (!interiorFocus) {
1014 IntRect focusRect(rect);
1015 focusRect.inflate(focusWidth + focusPad);
1016 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1024 #if GTK_CHECK_VERSION(3, 20, 0)
1025 static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1027 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1028 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1029 searchEntryWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
1030 searchEntryWidget.leftIcon().setState(GTK_STATE_FLAG_NORMAL);
1031 searchEntryWidget.rightIcon().setState(GTK_STATE_FLAG_NORMAL);
1033 // Get the icon size based on the font size.
1034 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1035 icon.setIconSize(style.computedFontPixelSize());
1036 IntSize preferredSize = icon.preferredSize();
1037 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1038 if (themePart == EntryIconLeft)
1039 preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom);
1041 preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom);
1042 style.setWidth(Length(preferredSize.width(), Fixed));
1043 style.setHeight(Length(preferredSize.height(), Fixed));
1046 // Defined in GTK+ (gtk/gtkiconfactory.c)
1047 static const gint gtkIconSizeMenu = 16;
1048 static const gint gtkIconSizeSmallToolbar = 18;
1049 static const gint gtkIconSizeButton = 20;
1050 static const gint gtkIconSizeLargeToolbar = 24;
1051 static const gint gtkIconSizeDnd = 32;
1052 static const gint gtkIconSizeDialog = 48;
1054 static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
1056 if (pixelSize < gtkIconSizeSmallToolbar)
1057 return GTK_ICON_SIZE_MENU;
1058 if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
1059 return GTK_ICON_SIZE_SMALL_TOOLBAR;
1060 if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
1061 return GTK_ICON_SIZE_BUTTON;
1062 if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
1063 return GTK_ICON_SIZE_LARGE_TOOLBAR;
1064 if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
1065 return GTK_ICON_SIZE_DND;
1067 return GTK_ICON_SIZE_DIALOG;
1070 static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1072 style.resetBorder();
1073 style.resetPadding();
1075 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1076 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1079 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1081 // Get the icon size based on the font size.
1082 int fontSize = style.computedFontPixelSize();
1083 if (fontSize < gtkIconSizeMenu) {
1084 style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed));
1085 style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed));
1088 gint width = 0, height = 0;
1089 gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
1090 style.setWidth(Length(width + (padding.left + padding.right), Fixed));
1091 style.setHeight(Length(height + (padding.top + padding.bottom), Fixed));
1095 bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r)
1097 return paintTextField(o, i, r);
1100 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
1102 adjustSearchFieldCancelButtonStyle(styleResolver, style, e);
1105 bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect)
1107 return paintSearchFieldResultsDecorationPart(o, i, rect);
1110 void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
1112 adjustSearchFieldIconStyle(EntryIconLeft, style);
1115 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1117 adjustSearchFieldIconStyle(EntryIconRight, style);
1120 #if GTK_CHECK_VERSION(3, 20, 0)
1121 static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1123 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1124 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1125 searchEntryWidget.entry().setState(themePartStateFlags(*theme, Entry, renderObject));
1126 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1127 icon.setState(themePartStateFlags(*theme, themePart, renderObject));
1128 icon.setIconSize(renderObject.style().computedFontPixelSize());
1129 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1130 IntRect iconRect = rect;
1131 if (themePart == EntryIconLeft) {
1132 iconRect.move(contentsBox.left, contentsBox.top);
1133 iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom);
1135 iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom);
1136 return !icon.render(paintInfo.context().platformContext()->cr(), iconRect);
1138 bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1140 return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect);
1143 bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1145 return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect);
1148 static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1150 GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height()));
1154 if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height())
1155 icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR));
1157 gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y());
1161 static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state)
1163 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1164 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1165 gtk_style_context_set_direction(context.get(), direction);
1166 gtk_style_context_set_state(context.get(), state);
1167 return paintIcon(context.get(), graphicsContext, rect, iconName);
1170 static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect)
1172 if (!renderObject.node())
1175 // Get the renderer of <input> element.
1176 Node* input = renderObject.node()->shadowHost();
1178 input = renderObject.node();
1179 if (!is<RenderBox>(*input->renderer()))
1182 // If possible center the y-coordinate of the rect vertically in the parent input element.
1183 // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
1184 // that are even, which looks in relation to the box text.
1185 IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox();
1187 // Make sure the scaled decoration stays square and will fit in its parent's box.
1188 int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
1189 IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
1193 bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1195 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1196 if (iconRect.isEmpty())
1199 return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1200 gtkIconStateFlags(this, renderObject));
1203 bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1205 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1206 if (iconRect.isEmpty())
1209 return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1210 gtkIconStateFlags(this, renderObject));
1212 #endif // GTK_CHECK_VERSION(3, 20, 0)
1214 void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, const Element*) const
1216 // We cannot give a proper rendering when border radius is active, unfortunately.
1217 style.resetBorderRadius();
1218 style.setLineHeight(RenderStyle::initialLineHeight());
1221 bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect)
1223 return paintTextField(o, i, rect);
1226 bool RenderThemeGtk::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const
1228 return element.isPasswordField();
1231 void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const
1233 style.setBoxShadow(nullptr);
1236 void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const
1238 RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
1239 style.setBoxShadow(nullptr);
1242 #if GTK_CHECK_VERSION(3, 20, 0)
1247 * ╰── contents
1250 * ╰── [highlight]
1252 bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1254 ControlPart part = renderObject.style().appearance();
1255 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1257 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1258 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1259 auto& scale = sliderWidget.scale();
1260 scale.setState(scaleState);
1261 auto& contents = sliderWidget.contents();
1262 auto& trough = sliderWidget.trough();
1263 trough.setState(scaleState);
1264 auto& slider = sliderWidget.slider();
1265 auto& highlight = sliderWidget.highlight();
1267 // The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it.
1268 IntSize preferredSize = scale.preferredSize();
1269 preferredSize = preferredSize.expandedTo(contents.preferredSize());
1270 preferredSize = preferredSize.expandedTo(trough.preferredSize());
1271 FloatRect trackRect = rect;
1272 if (part == SliderHorizontalPart) {
1273 trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2));
1274 trackRect.setHeight(preferredSize.height());
1276 trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0);
1277 trackRect.setWidth(preferredSize.width());
1280 FloatRect contentsRect;
1281 scale.render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect);
1282 contents.render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect);
1283 // Scale trough defines its size querying slider and highlight.
1284 if (part == SliderHorizontalPart)
1285 contentsRect.setHeight(trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height()));
1287 contentsRect.setWidth(trough.preferredSize().width() + std::max(slider.preferredSize().width(), highlight.preferredSize().width()));
1288 FloatRect troughRect = contentsRect;
1289 trough.render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect);
1290 if (isFocused(renderObject))
1291 trough.renderFocus(paintInfo.context().platformContext()->cr(), troughRect);
1293 LayoutPoint thumbLocation;
1294 if (is<HTMLInputElement>(renderObject.node())) {
1295 auto& input = downcast<HTMLInputElement>(*renderObject.node());
1296 if (auto* element = input.sliderThumbElement())
1297 thumbLocation = element->renderBox()->location();
1300 if (part == SliderHorizontalPart) {
1301 if (renderObject.style().direction() == TextDirection::RTL) {
1302 contentsRect.move(thumbLocation.x(), 0);
1303 contentsRect.setWidth(contentsRect.width() - thumbLocation.x());
1305 contentsRect.setWidth(thumbLocation.x());
1307 contentsRect.setHeight(thumbLocation.y());
1308 highlight.render(paintInfo.context().platformContext()->cr(), contentsRect);
1313 void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1315 ControlPart part = style.appearance();
1316 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1319 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1320 sliderWidget.scale().setState(GTK_STATE_FLAG_NORMAL);
1321 sliderWidget.trough().setState(GTK_STATE_FLAG_NORMAL);
1323 IntSize preferredSize = sliderWidget.scale().preferredSize();
1324 preferredSize = preferredSize.expandedTo(sliderWidget.contents().preferredSize());
1325 preferredSize = preferredSize.expandedTo(sliderWidget.trough().preferredSize());
1326 preferredSize = preferredSize.expandedTo(sliderWidget.slider().preferredSize());
1327 if (part == SliderThumbHorizontalPart) {
1328 style.setWidth(Length(preferredSize.width(), Fixed));
1329 style.setHeight(Length(preferredSize.height(), Fixed));
1332 ASSERT(part == SliderThumbVerticalPart);
1333 style.setWidth(Length(preferredSize.height(), Fixed));
1334 style.setHeight(Length(preferredSize.width(), Fixed));
1337 bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1339 ControlPart part = renderObject.style().appearance();
1340 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1342 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1343 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1344 auto& scale = sliderWidget.scale();
1345 scale.setState(scaleState);
1346 auto& contents = sliderWidget.contents();
1347 auto& trough = sliderWidget.trough();
1348 trough.setState(scaleState);
1349 auto& slider = sliderWidget.slider();
1350 slider.setState(themePartStateFlags(*this, ScaleSlider, renderObject));
1351 auto& highlight = sliderWidget.highlight();
1353 GtkBorder scaleContentsBox = scale.contentsBox();
1354 GtkBorder contentsContentsBox = contents.contentsBox();
1355 GtkBorder troughContentsBox = trough.contentsBox();
1357 padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left;
1358 padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right;
1359 padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top;
1360 padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom;
1362 // Scale trough defines its size querying slider and highlight.
1363 int troughHeight = trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height());
1364 IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight));
1365 sliderRect.move(padding.left, padding.top);
1366 sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom);
1367 slider.render(paintInfo.context().platformContext()->cr(), sliderRect);
1371 bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1373 ControlPart part = renderObject.style().appearance();
1374 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1376 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1377 gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1378 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get());
1379 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1381 if (!isEnabled(renderObject))
1382 gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE);
1384 IntRect sliderRect = rect;
1385 // GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and
1386 // the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes
1387 // and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look
1388 // good in other themes as well.
1389 static const int sliderSize = 4;
1391 if (part == SliderHorizontalPart) {
1392 sliderRect.setHeight(std::min(rect.height(), sliderSize));
1393 sliderRect.move(0, (rect.height() - sliderRect.height()) / 2);
1395 sliderRect.setWidth(std::min(rect.width(), sliderSize));
1396 sliderRect.move((rect.width() - sliderRect.width()) / 2, 0);
1399 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1400 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1402 if (isFocused(renderObject)) {
1403 gint focusWidth, focusPad;
1404 gtk_style_context_get_style(context.get(), "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1405 IntRect focusRect(sliderRect);
1406 focusRect.inflate(focusWidth + focusPad);
1407 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1413 void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1415 ControlPart part = style.appearance();
1416 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1419 GRefPtr<GtkStyleContext> context = createStyleContext(Scale);
1420 gint sliderWidth, sliderLength;
1421 gtk_style_context_get_style(context.get(), "slider-width", &sliderWidth, "slider-length", &sliderLength, nullptr);
1423 if (part == SliderThumbHorizontalPart) {
1424 style.setWidth(Length(sliderLength, Fixed));
1425 style.setHeight(Length(sliderWidth, Fixed));
1428 ASSERT(part == SliderThumbVerticalPart);
1429 style.setWidth(Length(sliderWidth, Fixed));
1430 style.setHeight(Length(sliderLength, Fixed));
1433 bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1435 ControlPart part = renderObject.style().appearance();
1436 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1438 // FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle.
1439 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1440 gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1441 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get());
1442 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get());
1443 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1446 if (!isEnabled(renderObject))
1447 flags |= GTK_STATE_FLAG_INSENSITIVE;
1448 else if (isHovered(renderObject))
1449 flags |= GTK_STATE_FLAG_PRELIGHT;
1450 if (isPressed(renderObject))
1451 flags |= GTK_STATE_FLAG_ACTIVE;
1452 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1454 gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
1455 part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
1461 #if GTK_CHECK_VERSION(3, 20, 0)
1462 IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
1464 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1465 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1466 IntSize preferredSize = progressBarWidget.progressBar().preferredSize();
1467 preferredSize = preferredSize.expandedTo(progressBarWidget.trough().preferredSize());
1468 preferredSize = preferredSize.expandedTo(progressBarWidget.progress().preferredSize());
1469 return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height());
1472 bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1474 if (!renderObject.isProgress())
1477 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1478 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1479 progressBarWidget.progressBar().render(paintInfo.context().platformContext()->cr(), rect);
1480 progressBarWidget.trough().render(paintInfo.context().platformContext()->cr(), rect);
1481 progressBarWidget.progress().render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect));
1485 IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const
1490 bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1492 if (!renderObject.isProgress())
1495 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar);
1496 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get());
1497 GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get());
1499 gtk_render_background(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1500 gtk_render_frame(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1502 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
1505 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1506 IntRect progressRect(
1507 rect.x() + padding.left,
1508 rect.y() + padding.top,
1509 rect.width() - (padding.left + padding.right),
1510 rect.height() - (padding.top + padding.bottom));
1511 progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
1513 if (!progressRect.isEmpty()) {
1514 #if GTK_CHECK_VERSION(3, 13, 7)
1515 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1516 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1518 gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1524 #endif // GTK_CHECK_VERSION(3, 20, 0)
1526 #if GTK_CHECK_VERSION(3, 20, 0)
1527 RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const
1529 return renderObject.style().direction() == TextDirection::RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight;
1532 void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1534 style.setWidth(Length(spinButtonSize().width(), Fixed));
1535 style.setHeight(Length(spinButtonSize().height(), Fixed));
1538 bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1540 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
1541 auto spinButtonState = themePartStateFlags(*this, SpinButton, renderObject);
1542 spinButtonWidget.spinButton().setState(spinButtonState);
1543 spinButtonWidget.entry().setState(spinButtonState);
1544 auto& up = spinButtonWidget.up();
1545 up.setState(themePartStateFlags(*this, SpinButtonUpButton, renderObject));
1546 auto& down = spinButtonWidget.down();
1547 down.setState(themePartStateFlags(*this, SpinButtonDownButton, renderObject));
1549 IntRect iconRect = rect;
1550 iconRect.setWidth(iconRect.width() / 2);
1551 if (renderObject.style().direction() == TextDirection::RTL)
1552 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1554 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1555 iconRect.move(iconRect.width(), 0);
1556 if (renderObject.style().direction() == TextDirection::RTL)
1557 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1559 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1564 RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const
1566 return InnerSpinButtonLayout::Vertical;
1568 static gint spinButtonArrowSize(GtkStyleContext* context)
1570 PangoFontDescription* fontDescription;
1571 gtk_style_context_get(context, gtk_style_context_get_state(context), "font", &fontDescription, nullptr);
1572 gint fontSize = pango_font_description_get_size(fontDescription);
1573 gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
1574 pango_font_description_free(fontDescription);
1576 return arrowSize - arrowSize % 2; // Force even.
1579 void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1581 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1584 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1586 int width = spinButtonArrowSize(context.get()) + padding.left + padding.right;
1587 style.setWidth(Length(width, Fixed));
1588 style.setMinWidth(Length(width, Fixed));
1591 static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
1593 ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);
1595 GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext);
1596 GtkTextDirection direction = gtk_style_context_get_direction(context.get());
1597 guint state = static_cast<guint>(gtk_style_context_get_state(context.get()));
1598 if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
1599 if (theme->isPressed(renderObject)) {
1600 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
1601 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
1602 state |= GTK_STATE_FLAG_ACTIVE;
1603 } else if (theme->isHovered(renderObject)) {
1604 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
1605 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
1606 state |= GTK_STATE_FLAG_PRELIGHT;
1609 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state));
1612 IntRect buttonRect(rect);
1613 guint junction = gtk_style_context_get_junction_sides(context.get());
1614 if (arrowType == GTK_ARROW_UP)
1615 junction |= GTK_JUNCTION_BOTTOM;
1617 junction |= GTK_JUNCTION_TOP;
1618 buttonRect.move(0, rect.height() / 2);
1620 buttonRect.setHeight(rect.height() / 2);
1621 gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction));
1623 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1624 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1626 // Paint arrow centered inside button.
1627 // This code is based on gtkspinbutton.c code.
1630 if (arrowType == GTK_ARROW_UP) {
1632 arrowRect.setY(rect.y());
1633 arrowRect.setHeight(rect.height() / 2 - 2);
1636 arrowRect.setY(rect.y() + buttonRect.y());
1637 arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
1639 arrowRect.setWidth(rect.width() - 3);
1640 if (direction == GTK_TEXT_DIR_LTR)
1641 arrowRect.setX(rect.x() + 1);
1643 arrowRect.setX(rect.x() + 2);
1645 gint width = arrowRect.width() / 2;
1646 width -= width % 2 - 1; // Force odd.
1647 gint height = (width + 1) / 2;
1649 arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
1650 gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
1653 bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1655 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1656 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1659 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
1660 flags |= GTK_STATE_FLAG_INSENSITIVE;
1661 else if (isFocused(renderObject))
1662 flags |= GTK_STATE_FLAG_FOCUSED;
1663 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1665 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP);
1666 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN);
1670 #endif // GTK_CHECK_VERSION(3, 20, 0)
1672 Seconds RenderThemeGtk::caretBlinkInterval() const
1674 GtkSettings* settings = gtk_settings_get_default();
1676 gboolean shouldBlink;
1679 g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, nullptr);
1684 return 500_us * time;
1687 enum StyleColorType { StyleColorBackground, StyleColorForeground };
1689 #if GTK_CHECK_VERSION(3, 20, 0)
1690 static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1692 RenderThemeGadget* gadget = nullptr;
1693 switch (themePart) {
1695 ASSERT_NOT_REACHED();
1698 gadget = &static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)).entry();
1700 case EntrySelection:
1701 gadget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SelectedEntry)).selection();
1704 gadget = &static_cast<RenderThemeListView&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ListView)).treeview();
1707 gadget = &static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Button)).button();
1712 gadget->setState(state);
1713 return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color();
1716 static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1718 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
1719 gtk_style_context_set_state(context.get(), state);
1721 GdkRGBA gdkRGBAColor;
1722 if (colorType == StyleColorBackground)
1723 gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor);
1725 gtk_style_context_get_color(context.get(), state, &gdkRGBAColor);
1726 return gdkRGBAColor;
1728 #endif // GTK_CHECK_VERSION(3, 20, 0)
1730 Color RenderThemeGtk::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1732 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1735 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1737 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1740 Color RenderThemeGtk::platformActiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1742 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1745 Color RenderThemeGtk::platformInactiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1747 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1750 Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1752 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1755 Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1757 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1760 Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1762 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1765 Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1767 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1770 Color RenderThemeGtk::systemColor(CSSValueID cssValueId, OptionSet<StyleColor::Options> options) const
1772 switch (cssValueId) {
1773 case CSSValueButtontext:
1774 return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1775 case CSSValueCaptiontext:
1776 return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1778 return RenderTheme::systemColor(cssValueId, options);
1782 void RenderThemeGtk::platformColorsDidChange()
1784 RenderTheme::platformColorsDidChange();
1788 String RenderThemeGtk::extraMediaControlsStyleSheet()
1790 return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
1793 #if ENABLE(FULLSCREEN_API)
1794 String RenderThemeGtk::extraFullScreenStyleSheet()
1800 #if GTK_CHECK_VERSION(3, 20, 0)
1801 bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1803 auto& iconWidget = static_cast<RenderThemeIcon&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Icon));
1804 auto& icon = static_cast<RenderThemeIconGadget&>(iconWidget.icon());
1805 icon.setState(themePartStateFlags(*this, MediaButton, renderObject));
1806 icon.setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
1807 icon.setIconName(iconName);
1808 return !icon.render(graphicsContext.platformContext()->cr(), rect);
1811 bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1813 GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton);
1814 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1815 gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject));
1816 static const unsigned mediaIconSize = 16;
1817 IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize);
1818 return !paintIcon(context.get(), graphicsContext, iconRect, iconName);
1820 #endif // GTK_CHECK_VERSION(3, 20, 0)
1822 bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const
1824 return (part != MediaMuteButtonPart);
1827 bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1829 return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic");
1832 bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1834 Node* node = renderObject.node();
1837 Node* mediaNode = node->shadowHost();
1838 if (!is<HTMLMediaElement>(mediaNode))
1841 HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode);
1842 return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
1845 bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1847 Node* node = renderObject.node();
1850 if (!nodeHasPseudo(*node, "-webkit-media-controls-play-button"))
1853 return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused") ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
1856 bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1858 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic");
1861 bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1863 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic");
1866 #if ENABLE(VIDEO_TRACK)
1867 bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1869 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic");
1873 static FloatRoundedRect::Radii borderRadiiFromStyle(const RenderStyle& style)
1875 return FloatRoundedRect::Radii(
1876 IntSize(style.borderTopLeftRadius().width.intValue(), style.borderTopLeftRadius().height.intValue()),
1877 IntSize(style.borderTopRightRadius().width.intValue(), style.borderTopRightRadius().height.intValue()),
1878 IntSize(style.borderBottomLeftRadius().width.intValue(), style.borderBottomLeftRadius().height.intValue()),
1879 IntSize(style.borderBottomRightRadius().width.intValue(), style.borderBottomRightRadius().height.intValue()));
1882 bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1884 auto mediaElement = parentMediaElement(o);
1888 GraphicsContext& context = paintInfo.context();
1890 context.setStrokeStyle(NoStroke);
1892 float mediaDuration = mediaElement->duration();
1893 float totalTrackWidth = r.width();
1894 auto& style = o.style();
1895 RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
1896 for (unsigned index = 0; index < timeRanges->length(); ++index) {
1897 float start = timeRanges->start(index).releaseReturnValue();
1898 float end = timeRanges->end(index).releaseReturnValue();
1899 float startRatio = start / mediaDuration;
1900 float lengthRatio = (end - start) / mediaDuration;
1904 IntRect rangeRect(r);
1905 rangeRect.setWidth(lengthRatio * totalTrackWidth);
1907 rangeRect.move(startRatio * totalTrackWidth, 0);
1908 context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1915 bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1917 auto& style = o.style();
1918 paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1922 bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1924 auto mediaElement = parentMediaElement(renderObject);
1928 float volume = mediaElement->muted() ? 0.0f : mediaElement->volume();
1932 GraphicsContext& context = paintInfo.context();
1934 context.setStrokeStyle(NoStroke);
1936 int rectHeight = rect.height();
1937 float trackHeight = rectHeight * volume;
1938 auto& style = renderObject.style();
1939 IntRect volumeRect(rect);
1940 volumeRect.move(0, rectHeight - trackHeight);
1941 volumeRect.setHeight(ceil(trackHeight));
1943 context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1949 bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1951 return paintMediaSliderThumb(renderObject, paintInfo, rect);
1954 String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
1956 return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
1959 bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&)
1965 void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, const Element*) const
1967 style.setBoxShadow(nullptr);
1970 // These values have been copied from RenderThemeChromiumSkia.cpp
1971 static const int progressActivityBlocks = 5;
1972 static const int progressAnimationFrames = 10;
1973 static const Seconds progressAnimationInterval { 125_ms };
1974 Seconds RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const
1976 return progressAnimationInterval;
1979 Seconds RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const
1981 return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
1984 IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect)
1986 IntRect progressRect(fullBarRect);
1987 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1988 if (renderProgress.isDeterminate()) {
1989 int progressWidth = progressRect.width() * renderProgress.position();
1990 if (renderObject.style().direction() == TextDirection::RTL)
1991 progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
1992 progressRect.setWidth(progressWidth);
1993 return progressRect;
1996 double animationProgress = renderProgress.animationProgress();
1998 // Never let the progress rect shrink smaller than 2 pixels.
1999 int newWidth = std::max(2, progressRect.width() / progressActivityBlocks);
2000 int movableWidth = progressRect.width() - newWidth;
2001 progressRect.setWidth(newWidth);
2003 // We want the first 0.5 units of the animation progress to represent the
2004 // forward motion and the second 0.5 units to represent the backward motion,
2005 // thus we multiply by two here to get the full sweep of the progress bar with
2007 if (animationProgress < 0.5)
2008 progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
2010 progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));
2011 return progressRect;
2014 String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
2019 if (fileList->length() > 1)
2020 return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
2023 if (fileList->length())
2024 string = FileSystem::pathGetFileName(fileList->item(0)->path());
2025 else if (multipleFilesAllowed)
2026 string = fileButtonNoFilesSelectedLabel();
2028 string = fileButtonNoFileSelectedLabel();
2030 return StringTruncator::centerTruncate(string, width, font);
2034 String RenderThemeGtk::mediaControlsScript()
2036 StringBuilder scriptBuilder;
2037 scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
2038 scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript));
2039 scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript));
2040 return scriptBuilder.toString();
2042 #endif // ENABLE(VIDEO)
2044 #endif // GTK_API_VERSION_2