blob: 9286cf6b5b6d424f692d0fc7b535d313dff4262c [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc.
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2008 Collabora Ltd.
* Copyright (C) 2009 Kenneth Rohde Christiansen
* Copyright (C) 2010 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "RenderThemeGtk.h"
#include "CSSValueKeywords.h"
#include "FileList.h"
#include "FloatRoundedRect.h"
#include "FontDescription.h"
#include "GRefPtrGtk.h"
#include "GUniquePtrGtk.h"
#include "Gradient.h"
#include "GraphicsContext.h"
#include "GtkVersioning.h"
#include "HTMLInputElement.h"
#include "HTMLMediaElement.h"
#include "LocalizedStrings.h"
#include "MediaControlElements.h"
#include "Page.h"
#include "PaintInfo.h"
#include "PlatformContextCairo.h"
#include "RenderBox.h"
#include "RenderObject.h"
#include "RenderProgress.h"
#include "RenderThemeWidget.h"
#include "ScrollbarThemeGtk.h"
#include "StringTruncator.h"
#include "TimeRanges.h"
#include "UserAgentScripts.h"
#include "UserAgentStyleSheets.h"
#include <cmath>
#include <gdk/gdk.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <wtf/FileSystem.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
RenderTheme& RenderTheme::singleton()
{
static NeverDestroyed<RenderThemeGtk> theme;
return theme;
}
static double getScreenDPI()
{
// FIXME: Really this should be the widget's screen.
GdkScreen* screen = gdk_screen_get_default();
if (!screen)
return 96; // Default to 96 DPI.
float dpi = gdk_screen_get_resolution(screen);
if (dpi <= 0)
return 96;
return dpi;
}
void RenderThemeGtk::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const
{
GtkSettings* settings = gtk_settings_get_default();
if (!settings)
return;
// This will be a font selection string like "Sans 10" so we cannot use it as the family name.
GUniqueOutPtr<gchar> fontName;
g_object_get(settings, "gtk-font-name", &fontName.outPtr(), nullptr);
if (!fontName || !fontName.get()[0])
return;
PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
if (!pangoDescription)
return;
fontDescription.setOneFamily(pango_font_description_get_family(pangoDescription));
int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
// If the size of the font is in points, we need to convert it to pixels.
if (!pango_font_description_get_size_is_absolute(pangoDescription))
size = size * (getScreenDPI() / 72.0);
fontDescription.setSpecifiedSize(size);
fontDescription.setIsAbsoluteSize(true);
fontDescription.setWeight(normalWeightValue());
fontDescription.setItalic(FontSelectionValue());
pango_font_description_free(pangoDescription);
}
#if ENABLE(DATALIST_ELEMENT)
IntSize RenderThemeGtk::sliderTickSize() const
{
// FIXME: We need to set this to the size of one tick mark.
return IntSize(0, 0);
}
int RenderThemeGtk::sliderTickOffsetFromTrackCenter() const
{
// FIXME: We need to set this to the position of the tick marks.
return 0;
}
#endif
#ifndef GTK_API_VERSION_2
static void themeChangedCallback()
{
Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
}
RenderThemeGtk::RenderThemeGtk()
{
static bool themeMonitorInitialized = false;
if (!themeMonitorInitialized) {
GtkSettings* settings = gtk_settings_get_default();
g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
g_signal_connect(settings, "notify::gtk-color-scheme", G_CALLBACK(themeChangedCallback), nullptr);
themeMonitorInitialized = true;
}
}
enum RenderThemePart {
Entry,
EntrySelection,
EntryIconLeft,
EntryIconRight,
Button,
CheckButton,
RadioButton,
ComboBox,
ComboBoxButton,
ComboBoxArrow,
Scale,
ScaleTrough,
ScaleSlider,
ProgressBar,
ProgressBarTrough,
ProgressBarProgress,
ListBox,
SpinButton,
SpinButtonUpButton,
SpinButtonDownButton,
#if ENABLE(VIDEO)
MediaButton,
#endif
#if GTK_CHECK_VERSION(3, 20, 0)
Window,
#endif
};
#if !GTK_CHECK_VERSION(3, 20, 0)
// This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c.
static const int minArrowSize = 15;
// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
static const int minSpinButtonArrowSize = 6;
static GRefPtr<GtkStyleContext> createStyleContext(RenderThemePart themePart, GtkStyleContext* parent = nullptr)
{
GRefPtr<GtkWidgetPath> path = adoptGRef(parent ? gtk_widget_path_copy(gtk_style_context_get_path(parent)) : gtk_widget_path_new());
switch (themePart) {
case Entry:
case EntrySelection:
gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_ENTRY);
break;
case EntryIconLeft:
case EntryIconRight:
gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
break;
case Button:
gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
break;
case CheckButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_CHECK_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_CHECK);
break;
case RadioButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_RADIO_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_RADIO);
break;
case ComboBox:
gtk_widget_path_append_type(path.get(), GTK_TYPE_COMBO_BOX);
break;
case ComboBoxButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
gtk_widget_path_iter_add_class(path.get(), -1, "combo");
break;
case ComboBoxArrow:
gtk_widget_path_append_type(path.get(), GTK_TYPE_ARROW);
gtk_widget_path_iter_add_class(path.get(), -1, "arrow");
break;
case Scale:
gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
break;
case ScaleTrough:
gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
break;
case ScaleSlider:
gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SLIDER);
break;
case ProgressBar:
gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
break;
case ProgressBarTrough:
gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
break;
case ProgressBarProgress:
gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
break;
case ListBox:
gtk_widget_path_append_type(path.get(), GTK_TYPE_TREE_VIEW);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_VIEW);
break;
case SpinButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
break;
case SpinButtonUpButton:
case SpinButtonDownButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
break;
#if ENABLE(VIDEO)
case MediaButton:
gtk_widget_path_append_type(path.get(), GTK_TYPE_IMAGE);
gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_IMAGE);
break;
#endif // ENABLE(VIDEO)
default:
ASSERT_NOT_REACHED();
break;
}
GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
gtk_style_context_set_path(context.get(), path.get());
gtk_style_context_set_parent(context.get(), parent);
return context;
}
static GRefPtr<GdkPixbuf> loadThemedIcon(GtkStyleContext* context, const char* iconName, GtkIconSize iconSize)
{
GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(iconName));
unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
#if GTK_CHECK_VERSION(3, 14, 0)
GtkTextDirection direction = gtk_style_context_get_direction(context);
if (direction & GTK_TEXT_DIR_LTR)
lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
else if (direction & GTK_TEXT_DIR_RTL)
lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
#endif
int width, height;
gtk_icon_size_lookup(iconSize, &width, &height);
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)));
if (!iconInfo)
return nullptr;
return adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), context, nullptr, nullptr));
}
#endif // !GTK_CHECK_VERSION(3, 20, 0)
#if ENABLE(VIDEO)
static bool nodeHasPseudo(Node& node, const char* pseudo)
{
return is<Element>(node) && downcast<Element>(node).pseudo() == pseudo;
}
static bool nodeHasClass(Node* node, const char* className)
{
if (!is<Element>(*node))
return false;
Element& element = downcast<Element>(*node);
if (!element.hasClass())
return false;
return element.classNames().contains(className);
}
#endif // ENABLE(VIDEO)
RenderThemeGtk::~RenderThemeGtk() = default;
static bool supportsFocus(ControlPart appearance)
{
switch (appearance) {
case PushButtonPart:
case ButtonPart:
case TextFieldPart:
case TextAreaPart:
case SearchFieldPart:
case MenulistPart:
case RadioPart:
case CheckboxPart:
case SliderHorizontalPart:
case SliderVerticalPart:
return true;
default:
return false;
}
}
bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const
{
return supportsFocus(style.appearance());
}
bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const
{
return isEnabled(o);
}
int RenderThemeGtk::baselinePosition(const RenderBox& box) const
{
// FIXME: This strategy is possibly incorrect for the GTK+ port.
if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
return box.marginTop() + box.height() - 2;
return RenderTheme::baselinePosition(box);
}
#if GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&)
{
}
static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject)
{
unsigned stateFlags = 0;
switch (renderObject.style().direction()) {
case TextDirection::RTL:
stateFlags |= GTK_STATE_FLAG_DIR_RTL;
break;
case TextDirection::LTR:
stateFlags |= GTK_STATE_FLAG_DIR_LTR;
break;
}
if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject)))
stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
else {
if (theme.isHovered(renderObject))
stateFlags |= GTK_STATE_FLAG_PRELIGHT;
if (theme.isFocused(renderObject))
stateFlags |= GTK_STATE_FLAG_FOCUSED;
}
switch (themePart) {
case CheckButton:
case RadioButton:
if (theme.isChecked(renderObject))
stateFlags |= GTK_STATE_FLAG_CHECKED;
if (theme.isIndeterminate(renderObject))
stateFlags |= GTK_STATE_FLAG_INCONSISTENT;
if (theme.isPressed(renderObject))
stateFlags |= GTK_STATE_FLAG_SELECTED;
break;
case Button:
case ComboBoxButton:
case ScaleSlider:
case EntryIconLeft:
case EntryIconRight:
#if ENABLE(VIDEO)
case MediaButton:
#endif
if (theme.isPressed(renderObject))
stateFlags |= GTK_STATE_FLAG_ACTIVE;
break;
case SpinButtonUpButton:
if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject))
stateFlags |= GTK_STATE_FLAG_ACTIVE;
if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject))
stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
break;
case SpinButtonDownButton:
if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject))
stateFlags |= GTK_STATE_FLAG_ACTIVE;
if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject))
stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
break;
default:
break;
}
return static_cast<GtkStateFlags>(stateFlags);
}
#else
static GtkTextDirection gtkTextDirection(TextDirection direction)
{
switch (direction) {
case TextDirection::RTL:
return GTK_TEXT_DIR_RTL;
case TextDirection::LTR:
return GTK_TEXT_DIR_LTR;
default:
return GTK_TEXT_DIR_NONE;
}
}
static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject)
{
if (!theme->isEnabled(renderObject))
return GTK_STATE_FLAG_INSENSITIVE;
if (theme->isPressed(renderObject))
return GTK_STATE_FLAG_ACTIVE;
if (theme->isHovered(renderObject))
return GTK_STATE_FLAG_PRELIGHT;
return GTK_STATE_FLAG_NORMAL;
}
static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
{
gint focusWidth, focusPad;
gtk_style_context_get_style(context, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
rect.inflate(focusWidth + focusPad);
}
void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
{
GRefPtr<GtkStyleContext> context;
bool checkInteriorFocus = false;
ControlPart part = renderObject.style().appearance();
switch (part) {
case CheckboxPart:
case RadioPart:
context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton);
gint indicatorSpacing;
gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
rect.inflate(indicatorSpacing);
return;
case SliderVerticalPart:
case SliderHorizontalPart:
context = createStyleContext(ScaleSlider);
break;
case ButtonPart:
case MenulistButtonPart:
case MenulistPart:
context = createStyleContext(Button);
checkInteriorFocus = true;
break;
case TextFieldPart:
case TextAreaPart:
context = createStyleContext(Entry);
checkInteriorFocus = true;
break;
default:
return;
}
ASSERT(context);
if (checkInteriorFocus) {
gboolean interiorFocus;
gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, nullptr);
if (interiorFocus)
return;
}
adjustRectForFocus(context.get(), rect);
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
// Some layout tests check explicitly that buttons ignore line-height.
if (style.appearance() == PushButtonPart)
style.setLineHeight(RenderStyle::initialLineHeight());
}
static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize)
{
if (rect.width() > minSize.width()) {
rect.inflateX(-(rect.width() - minSize.width()) / 2);
rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1.
}
if (rect.height() > minSize.height()) {
rect.inflateY(-(rect.height() - minSize.height()) / 2);
rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1.
}
}
#if GTK_CHECK_VERSION(3, 20, 0)
static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
{
ASSERT(themePart == CheckButton || themePart == RadioButton);
// The width and height are both specified, so we shouldn't change them.
if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
return;
auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
toggleWidget.button().setState(GTK_STATE_FLAG_NORMAL);
toggleWidget.toggle().setState(GTK_STATE_FLAG_NORMAL);
IntSize preferredSize = toggleWidget.button().preferredSize();
preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
if (style.width().isIntrinsicOrAuto())
style.setWidth(Length(preferredSize.width(), Fixed));
if (style.height().isAuto())
style.setHeight(Length(preferredSize.height(), Fixed));
}
static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
{
ASSERT(themePart == CheckButton || themePart == RadioButton);
auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
auto toggleState = themePartStateFlags(*theme, themePart, renderObject);
toggleWidget.button().setState(toggleState);
toggleWidget.toggle().setState(toggleState);
FloatRect rect = fullRect;
// Some themes do not render large toggle buttons properly, so we simply
// shrink the rectangle back down to the default size and then center it
// in the full toggle button region. The reason for not simply forcing toggle
// buttons to be a smaller size is that we don't want to break site layouts.
IntSize preferredSize = toggleWidget.button().preferredSize();
preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize);
toggleWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
toggleWidget.toggle().render(paintInfo.context().platformContext()->cr(), rect);
if (theme->isFocused(renderObject))
toggleWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
}
#else
static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
{
// The width and height are both specified, so we shouldn't change them.
if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
return;
GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
// Other ports hard-code this to 13. GTK+ users tend to demand the native look.
gint indicatorSize;
gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
if (style.width().isIntrinsicOrAuto())
style.setWidth(Length(indicatorSize, Fixed));
if (style.height().isAuto())
style.setHeight(Length(indicatorSize, Fixed));
}
static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
{
GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
unsigned flags = 0;
if (!theme->isEnabled(renderObject))
flags |= GTK_STATE_FLAG_INSENSITIVE;
else if (theme->isHovered(renderObject))
flags |= GTK_STATE_FLAG_PRELIGHT;
if (theme->isIndeterminate(renderObject))
flags |= GTK_STATE_FLAG_INCONSISTENT;
else if (theme->isChecked(renderObject))
#if GTK_CHECK_VERSION(3, 13, 7)
flags |= GTK_STATE_FLAG_CHECKED;
#else
flags |= GTK_STATE_FLAG_ACTIVE;
#endif
if (theme->isPressed(renderObject))
flags |= GTK_STATE_FLAG_SELECTED;
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
// Some themes do not render large toggle buttons properly, so we simply
// shrink the rectangle back down to the default size and then center it
// in the full toggle button region. The reason for not simply forcing toggle
// buttons to be a smaller size is that we don't want to break site layouts.
FloatRect rect(fullRect);
gint indicatorSize;
gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
IntSize minSize(indicatorSize, indicatorSize);
shrinkToMinimumSizeAndCenterRectangle(rect, minSize);
gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
if (themePart == CheckButton)
gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
else
gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
if (theme->isFocused(renderObject)) {
IntRect indicatorRect(rect);
gint indicatorSpacing;
gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
indicatorRect.inflate(indicatorSpacing);
gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
indicatorRect.width(), indicatorRect.height());
}
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
{
setToggleSize(CheckButton, style);
}
bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
paintToggle(this, CheckButton, renderObject, paintInfo, rect);
return false;
}
void RenderThemeGtk::setRadioSize(RenderStyle& style) const
{
setToggleSize(RadioButton, style);
}
bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
paintToggle(this, RadioButton, renderObject, paintInfo, rect);
return false;
}
#if GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
auto& buttonWidget = static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(isDefault(renderObject) ? RenderThemeWidget::Type::ButtonDefault : RenderThemeWidget::Type::Button));
buttonWidget.button().setState(themePartStateFlags(*this, Button, renderObject));
buttonWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
if (isFocused(renderObject))
buttonWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
return false;
}
#else
static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
IntRect buttonRect(rect);
guint flags = 0;
if (!theme->isEnabled(renderObject))
flags |= GTK_STATE_FLAG_INSENSITIVE;
else if (theme->isHovered(renderObject))
flags |= GTK_STATE_FLAG_PRELIGHT;
if (theme->isPressed(renderObject))
flags |= GTK_STATE_FLAG_ACTIVE;
gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
if (theme->isDefault(renderObject)) {
GtkBorder* borderPtr = 0;
GtkBorder border = { 1, 1, 1, 1 };
gtk_style_context_get_style(context, "default-border", &borderPtr, nullptr);
if (borderPtr) {
border = *borderPtr;
gtk_border_free(borderPtr);
}
buttonRect.move(border.left, border.top);
buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));
gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
}
gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
if (theme->isFocused(renderObject)) {
gint focusWidth, focusPad;
gboolean displaceFocus, interiorFocus;
gtk_style_context_get_style(
context,
"focus-line-width", &focusWidth,
"focus-padding", &focusPad,
"interior-focus", &interiorFocus,
"displace-focus", &displaceFocus,
nullptr);
if (interiorFocus) {
GtkBorder borderWidth;
gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth);
buttonRect = IntRect(
buttonRect.x() + borderWidth.left + focusPad,
buttonRect.y() + borderWidth.top + focusPad,
buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
} else
buttonRect.inflate(focusWidth + focusPad);
if (displaceFocus && theme->isPressed(renderObject)) {
gint childDisplacementX;
gint childDisplacementY;
gtk_style_context_get_style(context, "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
buttonRect.move(childDisplacementX, childDisplacementY);
}
gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
}
}
bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
GRefPtr<GtkStyleContext> context = createStyleContext(Button);
gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
renderButton(this, context.get(), renderObject, paintInfo, rect);
return false;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
static Color menuListColor(const Element* element)
{
#if GTK_CHECK_VERSION(3, 20, 0)
auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
GtkStateFlags state = element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
comboWidget.comboBox().setState(state);
comboWidget.button().setState(state);
return comboWidget.button().color();
#else
GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL);
GdkRGBA gdkRGBAColor;
gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor);
return gdkRGBAColor;
#endif // GTK_CHECK_VERSION(3, 20, 0)
}
void RenderThemeGtk::adjustMenuListStyle(StyleResolver&, RenderStyle& style, const Element* element) const
{
// The tests check explicitly that select menu buttons ignore line height.
style.setLineHeight(RenderStyle::initialLineHeight());
// We cannot give a proper rendering when border radius is active, unfortunately.
style.resetBorderRadius();
if (element)
style.setColor(menuListColor(element));
}
void RenderThemeGtk::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
{
adjustMenuListStyle(styleResolver, style, e);
}
#if GTK_CHECK_VERSION(3, 20, 0)
/*
* GtkComboBox gadgets tree
*
* combobox
* ├── box.linked
* │ ╰── button.combo
* │ ╰── box
* │ ├── cellview
* │ ╰── arrow
* ╰── window.popup
*/
LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
{
if (style.appearance() == NoControlPart)
return LengthBox(0);
auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
comboWidget.comboBox().setState(GTK_STATE_FLAG_NORMAL);
comboWidget.button().setState(GTK_STATE_FLAG_NORMAL);
comboWidget.arrow().setState(GTK_STATE_FLAG_NORMAL);
GtkBorder comboContentsBox = comboWidget.comboBox().contentsBox();
GtkBorder boxContentsBox = comboWidget.box().contentsBox();
GtkBorder buttonContentsBox = comboWidget.button().contentsBox();
GtkBorder buttonBoxContentsBox = comboWidget.buttonBox().contentsBox();
GtkBorder padding;
padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left;
padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right;
padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top;
padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom;
auto arrowSize = comboWidget.arrow().preferredSize();
return LengthBox(padding.top, padding.right + (style.direction() == TextDirection::LTR ? arrowSize.width() : 0),
padding.bottom, padding.left + (style.direction() == TextDirection::RTL ? arrowSize.width() : 0));
}
bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
auto comboState = themePartStateFlags(*this, ComboBoxButton, renderObject);
comboWidget.comboBox().setState(comboState);
comboWidget.button().setState(comboState);
comboWidget.arrow().setState(comboState);
cairo_t* cr = paintInfo.context().platformContext()->cr();
comboWidget.comboBox().render(cr, rect);
comboWidget.box().render(cr, rect);
FloatRect contentsRect;
comboWidget.button().render(cr, rect, &contentsRect);
comboWidget.buttonBox().render(cr, contentsRect);
comboWidget.arrow().render(cr, contentsRect);
if (isFocused(renderObject))
comboWidget.button().renderFocus(cr, rect);
return false;
}
#else
LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
{
if (style.appearance() == NoControlPart)
return { 0, 0, 0, 0 };
GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox);
GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get());
gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
GtkBorder borderWidth = { 0, 0, 0, 0 };
gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth);
gboolean interiorFocus;
gint focusWidth, focusPad;
gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
focusWidth = interiorFocus ? focusWidth + focusPad : 0;
return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == TextDirection::LTR ? minArrowSize : 0),
borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == TextDirection::RTL ? minArrowSize : 0) };
}
bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
{
// FIXME: adopt subpixel themes.
IntRect rect = IntRect(r);
cairo_t* cairoContext = paintInfo.context().platformContext()->cr();
GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
// Paint the button.
GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
gtk_style_context_set_direction(buttonStyleContext.get(), direction);
renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect);
// Get the inner rectangle.
gint focusWidth, focusPad;
GtkBorder* innerBorderPtr = 0;
GtkBorder innerBorder = { 1, 1, 1, 1 };
gtk_style_context_get_style(buttonStyleContext.get(), "inner-border", &innerBorderPtr, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
if (innerBorderPtr) {
innerBorder = *innerBorderPtr;
gtk_border_free(innerBorderPtr);
}
GtkBorder borderWidth;
GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get());
gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth);
focusWidth += focusPad;
IntRect innerRect(
rect.x() + innerBorder.left + borderWidth.left + focusWidth,
rect.y() + innerBorder.top + borderWidth.top + focusWidth,
rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));
if (isPressed(renderObject)) {
gint childDisplacementX;
gint childDisplacementY;
gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
innerRect.move(childDisplacementX, childDisplacementY);
}
innerRect.setWidth(std::max(1, innerRect.width()));
innerRect.setHeight(std::max(1, innerRect.height()));
// Paint the arrow.
GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get());
gtk_style_context_set_direction(arrowStyleContext.get(), direction);
gfloat arrowScaling;
gtk_style_context_get_style(parentStyleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
IntSize arrowSize(minArrowSize, innerRect.height());
FloatPoint arrowPosition(innerRect.location());
if (direction == GTK_TEXT_DIR_LTR)
arrowPosition.move(innerRect.width() - arrowSize.width(), 0);
// GTK+ actually fetches the xalign and valign values from the widget, but since we
// don't have a widget here, we are just using the default xalign and valign values of 0.5.
gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);
gtk_style_context_set_state(arrowStyleContext.get(), state);
gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);
return false;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect)
{
return paintMenuList(object, info, rect);
}
#if GTK_CHECK_VERSION(3, 20, 0)
static IntSize spinButtonSize()
{
auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
spinButtonWidget.spinButton().setState(GTK_STATE_FLAG_NORMAL);
spinButtonWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
spinButtonWidget.up().setState(GTK_STATE_FLAG_NORMAL);
spinButtonWidget.down().setState(GTK_STATE_FLAG_NORMAL);
IntSize preferredSize = spinButtonWidget.spinButton().preferredSize();
preferredSize = preferredSize.expandedTo(spinButtonWidget.entry().preferredSize());
IntSize upPreferredSize = preferredSize.expandedTo(spinButtonWidget.up().preferredSize());
IntSize downPreferredSize = preferredSize.expandedTo(spinButtonWidget.down().preferredSize());
return IntSize(upPreferredSize.width() + downPreferredSize.width(), std::max(upPreferredSize.height(), downPreferredSize.height()));
}
void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, const Element* element) const
{
if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element)))
return;
style.setMinHeight(Length(spinButtonSize().height(), Fixed));
// The default theme for the GTK+ port uses very wide spin buttons (66px) compared to what other
// browsers use (~13 px). And unfortunately, most of the web developers won't test how their site
// renders on WebKitGTK+. To ensure that spin buttons don't end up covering the values of the input
// field, we override the width of the input element and always increment it with the width needed
// for the spinbutton (when drawing the spinbutton).
int minimumWidth = style.width().intValue() + spinButtonSize().width();
style.setMinWidth(Length(minimumWidth, Fixed));
}
bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) {
auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
auto spinButtonState = themePartStateFlags(*this, Entry, renderObject);
spinButtonWidget.spinButton().setState(spinButtonState);
spinButtonWidget.entry().setState(spinButtonState);
spinButtonWidget.spinButton().render(paintInfo.context().platformContext()->cr(), rect);
spinButtonWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
} else {
auto& entryWidget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry));
entryWidget.entry().setState(themePartStateFlags(*this, Entry, renderObject));
entryWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
}
return false;
}
#else
void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const
{
}
bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
GRefPtr<GtkStyleContext> context = createStyleContext(Entry);
gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
guint flags = 0;
if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
flags |= GTK_STATE_FLAG_INSENSITIVE;
else if (isFocused(renderObject))
flags |= GTK_STATE_FLAG_FOCUSED;
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
if (isFocused(renderObject) && isEnabled(renderObject)) {
gboolean interiorFocus;
gint focusWidth, focusPad;
gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
if (!interiorFocus) {
IntRect focusRect(rect);
focusRect.inflate(focusWidth + focusPad);
gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
}
}
return false;
}
#endif
#if GTK_CHECK_VERSION(3, 20, 0)
static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
{
ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
searchEntryWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
searchEntryWidget.leftIcon().setState(GTK_STATE_FLAG_NORMAL);
searchEntryWidget.rightIcon().setState(GTK_STATE_FLAG_NORMAL);
// Get the icon size based on the font size.
auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
icon.setIconSize(style.computedFontPixelSize());
IntSize preferredSize = icon.preferredSize();
GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
if (themePart == EntryIconLeft)
preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom);
else
preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom);
style.setWidth(Length(preferredSize.width(), Fixed));
style.setHeight(Length(preferredSize.height(), Fixed));
}
#else
// Defined in GTK+ (gtk/gtkiconfactory.c)
static const gint gtkIconSizeMenu = 16;
static const gint gtkIconSizeSmallToolbar = 18;
static const gint gtkIconSizeButton = 20;
static const gint gtkIconSizeLargeToolbar = 24;
static const gint gtkIconSizeDnd = 32;
static const gint gtkIconSizeDialog = 48;
static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
{
if (pixelSize < gtkIconSizeSmallToolbar)
return GTK_ICON_SIZE_MENU;
if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
return GTK_ICON_SIZE_SMALL_TOOLBAR;
if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
return GTK_ICON_SIZE_BUTTON;
if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
return GTK_ICON_SIZE_LARGE_TOOLBAR;
if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
return GTK_ICON_SIZE_DND;
return GTK_ICON_SIZE_DIALOG;
}
static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
{
style.resetBorder();
style.resetPadding();
GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
GtkBorder padding;
gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
// Get the icon size based on the font size.
int fontSize = style.computedFontPixelSize();
if (fontSize < gtkIconSizeMenu) {
style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed));
style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed));
return;
}
gint width = 0, height = 0;
gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
style.setWidth(Length(width + (padding.left + padding.right), Fixed));
style.setHeight(Length(height + (padding.top + padding.bottom), Fixed));
}
#endif
bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r)
{
return paintTextField(o, i, r);
}
void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
{
adjustSearchFieldCancelButtonStyle(styleResolver, style, e);
}
bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect)
{
return paintSearchFieldResultsDecorationPart(o, i, rect);
}
void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
adjustSearchFieldIconStyle(EntryIconLeft, style);
}
void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
adjustSearchFieldIconStyle(EntryIconRight, style);
}
#if GTK_CHECK_VERSION(3, 20, 0)
static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
searchEntryWidget.entry().setState(themePartStateFlags(*theme, Entry, renderObject));
auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
icon.setState(themePartStateFlags(*theme, themePart, renderObject));
icon.setIconSize(renderObject.style().computedFontPixelSize());
GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
IntRect iconRect = rect;
if (themePart == EntryIconLeft) {
iconRect.move(contentsBox.left, contentsBox.top);
iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom);
} else
iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom);
return !icon.render(paintInfo.context().platformContext()->cr(), iconRect);
}
bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect);
}
bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect);
}
#else
static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height()));
if (!icon)
return false;
if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height())
icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR));
gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y());
return true;
}
static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state)
{
GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
gtk_style_context_set_direction(context.get(), direction);
gtk_style_context_set_state(context.get(), state);
return paintIcon(context.get(), graphicsContext, rect, iconName);
}
static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect)
{
if (!renderObject.node())
return IntRect();
// Get the renderer of <input> element.
Node* input = renderObject.node()->shadowHost();
if (!input)
input = renderObject.node();
if (!is<RenderBox>(*input->renderer()))
return IntRect();
// If possible center the y-coordinate of the rect vertically in the parent input element.
// We also add one pixel here to ensure that the y coordinate is rounded up for box heights
// that are even, which looks in relation to the box text.
IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox();
// Make sure the scaled decoration stays square and will fit in its parent's box.
int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
return scaledRect;
}
bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
if (iconRect.isEmpty())
return true;
return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
gtkIconStateFlags(this, renderObject));
}
bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
if (iconRect.isEmpty())
return true;
return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
gtkIconStateFlags(this, renderObject));
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
// We cannot give a proper rendering when border radius is active, unfortunately.
style.resetBorderRadius();
style.setLineHeight(RenderStyle::initialLineHeight());
}
bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect)
{
return paintTextField(o, i, rect);
}
bool RenderThemeGtk::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const
{
return element.isPasswordField();
}
void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
style.setBoxShadow(nullptr);
}
void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const
{
RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
style.setBoxShadow(nullptr);
}
#if GTK_CHECK_VERSION(3, 20, 0)
/*
* GtkScale
*
* scale
* ╰── contents
* ╰── trough
* ├── slider
* ╰── [highlight]
*/
bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
ControlPart part = renderObject.style().appearance();
ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
auto scaleState = themePartStateFlags(*this, Scale, renderObject);
auto& scale = sliderWidget.scale();
scale.setState(scaleState);
auto& contents = sliderWidget.contents();
auto& trough = sliderWidget.trough();
trough.setState(scaleState);
auto& slider = sliderWidget.slider();
auto& highlight = sliderWidget.highlight();
// The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it.
IntSize preferredSize = scale.preferredSize();
preferredSize = preferredSize.expandedTo(contents.preferredSize());
preferredSize = preferredSize.expandedTo(trough.preferredSize());
FloatRect trackRect = rect;
if (part == SliderHorizontalPart) {
trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2));
trackRect.setHeight(preferredSize.height());
} else {
trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0);
trackRect.setWidth(preferredSize.width());
}
FloatRect contentsRect;
scale.render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect);
contents.render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect);
// Scale trough defines its size querying slider and highlight.
if (part == SliderHorizontalPart)
contentsRect.setHeight(trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height()));
else
contentsRect.setWidth(trough.preferredSize().width() + std::max(slider.preferredSize().width(), highlight.preferredSize().width()));
FloatRect troughRect = contentsRect;
trough.render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect);
if (isFocused(renderObject))
trough.renderFocus(paintInfo.context().platformContext()->cr(), troughRect);
LayoutPoint thumbLocation;
if (is<HTMLInputElement>(renderObject.node())) {
auto& input = downcast<HTMLInputElement>(*renderObject.node());
if (auto* element = input.sliderThumbElement())
thumbLocation = element->renderBox()->location();
}
if (part == SliderHorizontalPart) {
if (renderObject.style().direction() == TextDirection::RTL) {
contentsRect.move(thumbLocation.x(), 0);
contentsRect.setWidth(contentsRect.width() - thumbLocation.x());
} else
contentsRect.setWidth(thumbLocation.x());
} else
contentsRect.setHeight(thumbLocation.y());
highlight.render(paintInfo.context().platformContext()->cr(), contentsRect);
return false;
}
void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
{
ControlPart part = style.appearance();
if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
return;
auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
sliderWidget.scale().setState(GTK_STATE_FLAG_NORMAL);
sliderWidget.trough().setState(GTK_STATE_FLAG_NORMAL);
IntSize preferredSize = sliderWidget.scale().preferredSize();
preferredSize = preferredSize.expandedTo(sliderWidget.contents().preferredSize());
preferredSize = preferredSize.expandedTo(sliderWidget.trough().preferredSize());
preferredSize = preferredSize.expandedTo(sliderWidget.slider().preferredSize());
if (part == SliderThumbHorizontalPart) {
style.setWidth(Length(preferredSize.width(), Fixed));
style.setHeight(Length(preferredSize.height(), Fixed));
return;
}
ASSERT(part == SliderThumbVerticalPart);
style.setWidth(Length(preferredSize.height(), Fixed));
style.setHeight(Length(preferredSize.width(), Fixed));
}
bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
ControlPart part = renderObject.style().appearance();
ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
auto scaleState = themePartStateFlags(*this, Scale, renderObject);
auto& scale = sliderWidget.scale();
scale.setState(scaleState);
auto& contents = sliderWidget.contents();
auto& trough = sliderWidget.trough();
trough.setState(scaleState);
auto& slider = sliderWidget.slider();
slider.setState(themePartStateFlags(*this, ScaleSlider, renderObject));
auto& highlight = sliderWidget.highlight();
GtkBorder scaleContentsBox = scale.contentsBox();
GtkBorder contentsContentsBox = contents.contentsBox();
GtkBorder troughContentsBox = trough.contentsBox();
GtkBorder padding;
padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left;
padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right;
padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top;
padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom;
// Scale trough defines its size querying slider and highlight.
int troughHeight = trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height());
IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight));
sliderRect.move(padding.left, padding.top);
sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom);
slider.render(paintInfo.context().platformContext()->cr(), sliderRect);
return false;
}
#else
bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
ControlPart part = renderObject.style().appearance();
ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get());
gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
if (!isEnabled(renderObject))
gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE);
IntRect sliderRect = rect;
// GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and
// the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes
// and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look
// good in other themes as well.
static const int sliderSize = 4;
if (part == SliderHorizontalPart) {
sliderRect.setHeight(std::min(rect.height(), sliderSize));
sliderRect.move(0, (rect.height() - sliderRect.height()) / 2);
} else {
sliderRect.setWidth(std::min(rect.width(), sliderSize));
sliderRect.move((rect.width() - sliderRect.width()) / 2, 0);
}
gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
if (isFocused(renderObject)) {
gint focusWidth, focusPad;
gtk_style_context_get_style(context.get(), "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
IntRect focusRect(sliderRect);
focusRect.inflate(focusWidth + focusPad);
gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
}
return false;
}
void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
{
ControlPart part = style.appearance();
if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
return;
GRefPtr<GtkStyleContext> context = createStyleContext(Scale);
gint sliderWidth, sliderLength;
gtk_style_context_get_style(context.get(), "slider-width", &sliderWidth, "slider-length", &sliderLength, nullptr);
if (part == SliderThumbHorizontalPart) {
style.setWidth(Length(sliderLength, Fixed));
style.setHeight(Length(sliderWidth, Fixed));
return;
}
ASSERT(part == SliderThumbVerticalPart);
style.setWidth(Length(sliderWidth, Fixed));
style.setHeight(Length(sliderLength, Fixed));
}
bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
ControlPart part = renderObject.style().appearance();
ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
// FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle.
GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get());
GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get());
gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
guint flags = 0;
if (!isEnabled(renderObject))
flags |= GTK_STATE_FLAG_INSENSITIVE;
else if (isHovered(renderObject))
flags |= GTK_STATE_FLAG_PRELIGHT;
if (isPressed(renderObject))
flags |= GTK_STATE_FLAG_ACTIVE;
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
return false;
}
#endif
#if GTK_CHECK_VERSION(3, 20, 0)
IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
{
const auto& renderProgress = downcast<RenderProgress>(renderObject);
auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
IntSize preferredSize = progressBarWidget.progressBar().preferredSize();
preferredSize = preferredSize.expandedTo(progressBarWidget.trough().preferredSize());
preferredSize = preferredSize.expandedTo(progressBarWidget.progress().preferredSize());
return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height());
}
bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
if (!renderObject.isProgress())
return true;
const auto& renderProgress = downcast<RenderProgress>(renderObject);
auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
progressBarWidget.progressBar().render(paintInfo.context().platformContext()->cr(), rect);
progressBarWidget.trough().render(paintInfo.context().platformContext()->cr(), rect);
progressBarWidget.progress().render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect));
return false;
}
#else
IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const
{
return bounds;
}
bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
if (!renderObject.isProgress())
return true;
GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar);
GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get());
GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get());
gtk_render_background(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
gtk_render_frame(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
GtkBorder padding;
gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
IntRect progressRect(
rect.x() + padding.left,
rect.y() + padding.top,
rect.width() - (padding.left + padding.right),
rect.height() - (padding.top + padding.bottom));
progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
if (!progressRect.isEmpty()) {
#if GTK_CHECK_VERSION(3, 13, 7)
gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
#else
gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
#endif
}
return false;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
#if GTK_CHECK_VERSION(3, 20, 0)
RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const
{
return renderObject.style().direction() == TextDirection::RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight;
}
void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
style.setWidth(Length(spinButtonSize().width(), Fixed));
style.setHeight(Length(spinButtonSize().height(), Fixed));
}
bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
auto spinButtonState = themePartStateFlags(*this, SpinButton, renderObject);
spinButtonWidget.spinButton().setState(spinButtonState);
spinButtonWidget.entry().setState(spinButtonState);
auto& up = spinButtonWidget.up();
up.setState(themePartStateFlags(*this, SpinButtonUpButton, renderObject));
auto& down = spinButtonWidget.down();
down.setState(themePartStateFlags(*this, SpinButtonDownButton, renderObject));
IntRect iconRect = rect;
iconRect.setWidth(iconRect.width() / 2);
if (renderObject.style().direction() == TextDirection::RTL)
up.render(paintInfo.context().platformContext()->cr(), iconRect);
else
down.render(paintInfo.context().platformContext()->cr(), iconRect);
iconRect.move(iconRect.width(), 0);
if (renderObject.style().direction() == TextDirection::RTL)
down.render(paintInfo.context().platformContext()->cr(), iconRect);
else
up.render(paintInfo.context().platformContext()->cr(), iconRect);
return false;
}
#else
RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const
{
return InnerSpinButtonLayout::Vertical;
}
static gint spinButtonArrowSize(GtkStyleContext* context)
{
PangoFontDescription* fontDescription;
gtk_style_context_get(context, gtk_style_context_get_state(context), "font", &fontDescription, nullptr);
gint fontSize = pango_font_description_get_size(fontDescription);
gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
pango_font_description_free(fontDescription);
return arrowSize - arrowSize % 2; // Force even.
}
void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
GtkBorder padding;
gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
int width = spinButtonArrowSize(context.get()) + padding.left + padding.right;
style.setWidth(Length(width, Fixed));
style.setMinWidth(Length(width, Fixed));
}
static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
{
ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);
GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext);
GtkTextDirection direction = gtk_style_context_get_direction(context.get());
guint state = static_cast<guint>(gtk_style_context_get_state(context.get()));
if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
if (theme->isPressed(renderObject)) {
if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
|| (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
state |= GTK_STATE_FLAG_ACTIVE;
} else if (theme->isHovered(renderObject)) {
if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
|| (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
state |= GTK_STATE_FLAG_PRELIGHT;
}
}
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state));
// Paint button.
IntRect buttonRect(rect);
guint junction = gtk_style_context_get_junction_sides(context.get());
if (arrowType == GTK_ARROW_UP)
junction |= GTK_JUNCTION_BOTTOM;
else {
junction |= GTK_JUNCTION_TOP;
buttonRect.move(0, rect.height() / 2);
}
buttonRect.setHeight(rect.height() / 2);
gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction));
gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
// Paint arrow centered inside button.
// This code is based on gtkspinbutton.c code.
IntRect arrowRect;
gdouble angle;
if (arrowType == GTK_ARROW_UP) {
angle = 0;
arrowRect.setY(rect.y());
arrowRect.setHeight(rect.height() / 2 - 2);
} else {
angle = G_PI;
arrowRect.setY(rect.y() + buttonRect.y());
arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
}
arrowRect.setWidth(rect.width() - 3);
if (direction == GTK_TEXT_DIR_LTR)
arrowRect.setX(rect.x() + 1);
else
arrowRect.setX(rect.x() + 2);
gint width = arrowRect.width() / 2;
width -= width % 2 - 1; // Force odd.
gint height = (width + 1) / 2;
arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
}
bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
guint flags = 0;
if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
flags |= GTK_STATE_FLAG_INSENSITIVE;
else if (isFocused(renderObject))
flags |= GTK_STATE_FLAG_FOCUSED;
gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP);
paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN);
return false;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
Seconds RenderThemeGtk::caretBlinkInterval() const
{
GtkSettings* settings = gtk_settings_get_default();
gboolean shouldBlink;
gint time;
g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, nullptr);
if (!shouldBlink)
return 0_s;
return 500_us * time;
}
enum StyleColorType { StyleColorBackground, StyleColorForeground };
#if GTK_CHECK_VERSION(3, 20, 0)
static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
{
RenderThemeGadget* gadget = nullptr;
switch (themePart) {
default:
ASSERT_NOT_REACHED();
FALLTHROUGH;
case Entry:
gadget = &static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)).entry();
break;
case EntrySelection:
gadget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SelectedEntry)).selection();
break;
case ListBox:
gadget = &static_cast<RenderThemeListView&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ListView)).treeview();
break;
case Button:
gadget = &static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Button)).button();
break;
case Window:
gadget = &static_cast<RenderThemeWindow&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Window)).window();
break;
}
ASSERT(gadget);
gadget->setState(state);
return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color();
}
#else
static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
{
GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
gtk_style_context_set_state(context.get(), state);
GdkRGBA gdkRGBAColor;
if (colorType == StyleColorBackground)
gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor);
else
gtk_style_context_get_color(context.get(), state, &gdkRGBAColor);
return gdkRGBAColor;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
Color RenderThemeGtk::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
}
Color RenderThemeGtk::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
}
Color RenderThemeGtk::platformActiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
}
Color RenderThemeGtk::platformInactiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
}
Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
}
Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
}
Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
}
Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
{
return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
}
Color RenderThemeGtk::disabledTextColor(const Color&, const Color&) const
{
return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground);
}
Color RenderThemeGtk::systemColor(CSSValueID cssValueId, OptionSet<StyleColor::Options> options) const
{
switch (cssValueId) {
case CSSValueButtontext:
return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
case CSSValueCaptiontext:
return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
case CSSValueText:
return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
case CSSValueGraytext:
return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground);
case CSSValueWebkitControlBackground:
return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorBackground);
#if GTK_CHECK_VERSION(3, 20, 0)
case CSSValueWindow: {
// Only get window color from the theme in dark mode.
gboolean preferDarkTheme = FALSE;
if (auto* settings = gtk_settings_get_default())
g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme, nullptr);
if (preferDarkTheme)
return styleColor(Window, GTK_STATE_FLAG_ACTIVE, StyleColorBackground);
break;
}
#endif
default:
break;
}
return RenderTheme::systemColor(cssValueId, options);
}
void RenderThemeGtk::platformColorsDidChange()
{
RenderTheme::platformColorsDidChange();
}
#if ENABLE(VIDEO)
String RenderThemeGtk::extraMediaControlsStyleSheet()
{
return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
}
#if ENABLE(FULLSCREEN_API)
String RenderThemeGtk::extraFullScreenStyleSheet()
{
return String();
}
#endif
#if GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
auto& iconWidget = static_cast<RenderThemeIcon&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Icon));
auto& icon = static_cast<RenderThemeIconGadget&>(iconWidget.icon());
icon.setState(themePartStateFlags(*this, MediaButton, renderObject));
icon.setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
icon.setIconName(iconName);
return !icon.render(graphicsContext.platformContext()->cr(), rect);
}
#else
bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton);
gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject));
static const unsigned mediaIconSize = 16;
IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize);
return !paintIcon(context.get(), graphicsContext, iconRect, iconName);
}
#endif // GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const
{
return (part != MediaMuteButtonPart);
}
bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic");
}
bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
Node* node = renderObject.node();
if (!node)
return true;
Node* mediaNode = node->shadowHost();
if (!is<HTMLMediaElement>(mediaNode))
return true;
HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode);
return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
}
bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
Node* node = renderObject.node();
if (!node)
return true;
if (!nodeHasPseudo(*node, "-webkit-media-controls-play-button"))
return true;
return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused") ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
}
bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic");
}
bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic");
}
#if ENABLE(VIDEO_TRACK)
bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic");
}
#endif
static FloatRoundedRect::Radii borderRadiiFromStyle(const RenderStyle& style)
{
return FloatRoundedRect::Radii(
IntSize(style.borderTopLeftRadius().width.intValue(), style.borderTopLeftRadius().height.intValue()),
IntSize(style.borderTopRightRadius().width.intValue(), style.borderTopRightRadius().height.intValue()),
IntSize(style.borderBottomLeftRadius().width.intValue(), style.borderBottomLeftRadius().height.intValue()),
IntSize(style.borderBottomRightRadius().width.intValue(), style.borderBottomRightRadius().height.intValue()));
}
bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
{
auto mediaElement = parentMediaElement(o);
if (!mediaElement)
return true;
GraphicsContext& context = paintInfo.context();
context.save();
context.setStrokeStyle(NoStroke);
float mediaDuration = mediaElement->duration();
float totalTrackWidth = r.width();
auto& style = o.style();
RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
for (unsigned index = 0; index < timeRanges->length(); ++index) {
float start = timeRanges->start(index).releaseReturnValue();
float end = timeRanges->end(index).releaseReturnValue();
float startRatio = start / mediaDuration;
float lengthRatio = (end - start) / mediaDuration;
if (!lengthRatio)
continue;
IntRect rangeRect(r);
rangeRect.setWidth(lengthRatio * totalTrackWidth);
if (index)
rangeRect.move(startRatio * totalTrackWidth, 0);
context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
}
context.restore();
return false;
}
bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
{
auto& style = o.style();
paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
return false;
}
bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
auto mediaElement = parentMediaElement(renderObject);
if (!mediaElement)
return true;
float volume = mediaElement->muted() ? 0.0f : mediaElement->volume();
if (!volume)
return true;
GraphicsContext& context = paintInfo.context();
context.save();
context.setStrokeStyle(NoStroke);
int rectHeight = rect.height();
float trackHeight = rectHeight * volume;
auto& style = renderObject.style();
IntRect volumeRect(rect);
volumeRect.move(0, rectHeight - trackHeight);
volumeRect.setHeight(ceil(trackHeight));
context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
context.restore();
return false;
}
bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
return paintMediaSliderThumb(renderObject, paintInfo, rect);
}
String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
{
return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
}
bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&)
{
return false;
}
#endif
void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, const Element*) const
{
style.setBoxShadow(nullptr);
}
// These values have been copied from RenderThemeChromiumSkia.cpp
static const int progressActivityBlocks = 5;
static const int progressAnimationFrames = 10;
static const Seconds progressAnimationInterval { 125_ms };
Seconds RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const
{
return progressAnimationInterval;
}
Seconds RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const
{
return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
}
IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect)
{
IntRect progressRect(fullBarRect);
const auto& renderProgress = downcast<RenderProgress>(renderObject);
if (renderProgress.isDeterminate()) {
int progressWidth = progressRect.width() * renderProgress.position();
if (renderObject.style().direction() == TextDirection::RTL)
progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
progressRect.setWidth(progressWidth);
return progressRect;
}
double animationProgress = renderProgress.animationProgress();
// Never let the progress rect shrink smaller than 2 pixels.
int newWidth = std::max(2, progressRect.width() / progressActivityBlocks);
int movableWidth = progressRect.width() - newWidth;
progressRect.setWidth(newWidth);
// We want the first 0.5 units of the animation progress to represent the
// forward motion and the second 0.5 units to represent the backward motion,
// thus we multiply by two here to get the full sweep of the progress bar with
// each direction.
if (animationProgress < 0.5)
progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
else
progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));
return progressRect;
}
String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
{
if (width <= 0)
return String();
if (fileList->length() > 1)
return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
String string;
if (fileList->length())
string = FileSystem::pathGetFileName(fileList->item(0)->path());
else if (multipleFilesAllowed)
string = fileButtonNoFilesSelectedLabel();
else
string = fileButtonNoFileSelectedLabel();
return StringTruncator::centerTruncate(string, width, font);
}
#if ENABLE(VIDEO)
String RenderThemeGtk::mediaControlsScript()
{
StringBuilder scriptBuilder;
scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript));
scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript));
return scriptBuilder.toString();
}
#endif // ENABLE(VIDEO)
#endif // GTK_API_VERSION_2
}