| /* |
| * 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 "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 |
| |
| 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 |
| Window, |
| }; |
| |
| #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); |
| } |
| |
| 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); |
| } |
| |
| void RenderThemeGtk::adjustButtonStyle(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. |
| } |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| static Color menuListColor(const Element* element) |
| { |
| 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(); |
| } |
| |
| void RenderThemeGtk::adjustMenuListStyle(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(RenderStyle& style, const Element* e) const |
| { |
| adjustMenuListStyle(style, e); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect) |
| { |
| return paintMenuList(object, info, rect); |
| } |
| |
| 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(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); |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| if (is<HTMLInputElement>(renderObject.generatingNode())) { |
| const auto& input = downcast<HTMLInputElement>(*(renderObject.generatingNode())); |
| if (input.list()) |
| paintListButtonForInput(renderObject, paintInfo, rect); |
| } |
| #endif |
| } |
| return false; |
| } |
| |
| 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)); |
| } |
| |
| bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r) |
| { |
| return paintTextField(o, i, r); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(RenderStyle& style, const Element* e) const |
| { |
| adjustSearchFieldCancelButtonStyle(style, e); |
| } |
| |
| bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect) |
| { |
| return paintSearchFieldResultsDecorationPart(o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(RenderStyle& style, const Element*) const |
| { |
| adjustSearchFieldIconStyle(EntryIconLeft, style); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(RenderStyle& style, const Element*) const |
| { |
| adjustSearchFieldIconStyle(EntryIconRight, style); |
| } |
| |
| 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); |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| void RenderThemeGtk::adjustListButtonStyle(RenderStyle& style, const Element*) const |
| { |
| // Add a margin to place the button at end of the input field. |
| if (style.isLeftToRightDirection()) |
| style.setMarginRight(Length(-4, Fixed)); |
| else |
| style.setMarginLeft(Length(-4, Fixed)); |
| } |
| |
| void RenderThemeGtk::paintListButtonForInput(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect) |
| { |
| // Use a combo box widget to render its arrow. |
| auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox)); |
| comboWidget.arrow().setState(themePartStateFlags(*this, ComboBoxButton, renderObject)); |
| |
| // But a search entry widget to get the contents rect, since this is a text input field. |
| auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry)); |
| auto& icon = static_cast<RenderThemeIconGadget&>(searchEntryWidget.rightIcon()); |
| icon.setIconSize(comboWidget.arrow().preferredSize().width()); |
| GtkBorder contentsBox = searchEntryWidget.entry().contentsBox(); |
| FloatRect adjustedRect(rect); |
| adjustedRect.move(contentsBox.left, contentsBox.top); |
| adjustedRect.contract(contentsBox.right + 1, contentsBox.top + contentsBox.bottom); |
| comboWidget.arrow().render(paintInfo.context().platformContext()->cr(), adjustedRect); |
| } |
| #endif |
| |
| void RenderThemeGtk::adjustSearchFieldStyle(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(RenderStyle& style, const Element*) const |
| { |
| style.setBoxShadow(nullptr); |
| } |
| |
| void RenderThemeGtk::adjustSliderThumbStyle(RenderStyle& style, const Element* element) const |
| { |
| RenderTheme::adjustSliderThumbStyle(style, element); |
| style.setBoxShadow(nullptr); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| 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; |
| } |
| |
| RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const |
| { |
| return renderObject.style().direction() == TextDirection::RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight; |
| } |
| |
| void RenderThemeGtk::adjustInnerSpinButtonStyle(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; |
| } |
| |
| 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 }; |
| |
| 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(); |
| } |
| |
| 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); |
| 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; |
| } |
| 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 |
| |
| 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); |
| } |
| |
| 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(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.appendCharacters(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript)); |
| scriptBuilder.appendCharacters(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript)); |
| scriptBuilder.appendCharacters(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript)); |
| return scriptBuilder.toString(); |
| } |
| #endif // ENABLE(VIDEO) |
| } |