blob: 73e0758a6edd1e193231083f89a7e7fdf20d1379 [file] [log] [blame]
/*
* Copyright (C) 2016 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "RenderThemeGadget.h"
#include "FloatRect.h"
#include "GRefPtrGtk.h"
namespace WebCore {
std::unique_ptr<RenderThemeGadget> RenderThemeGadget::create(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
{
switch (info.type) {
case RenderThemeGadget::Type::Generic:
return makeUnique<RenderThemeGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::TextField:
return makeUnique<RenderThemeTextFieldGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::Radio:
case RenderThemeGadget::Type::Check:
return makeUnique<RenderThemeToggleGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::Arrow:
return makeUnique<RenderThemeArrowGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::Icon:
return makeUnique<RenderThemeIconGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::Scrollbar:
return makeUnique<RenderThemeScrollbarGadget>(info, parent, siblings, position);
case RenderThemeGadget::Type::Button:
return makeUnique<RenderThemeButtonGadget>(info, parent, siblings, position);
}
ASSERT_NOT_REACHED();
return nullptr;
}
static GRefPtr<GtkStyleContext> createStyleContext(GtkWidgetPath* path, GtkStyleContext* parent)
{
GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
gtk_style_context_set_path(context.get(), path);
gtk_style_context_set_parent(context.get(), parent);
return context;
}
static void appendElementToPath(GtkWidgetPath* path, const RenderThemeGadget::Info& info)
{
// Scrollbars need to use its GType to be able to get non-CSS style properties.
gtk_widget_path_append_type(path, info.type == RenderThemeGadget::Type::Scrollbar ? GTK_TYPE_SCROLLBAR : G_TYPE_NONE);
gtk_widget_path_iter_set_object_name(path, -1, info.name);
for (const auto* className : info.classList)
gtk_widget_path_iter_add_class(path, -1, className);
}
RenderThemeGadget::RenderThemeGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
{
GRefPtr<GtkWidgetPath> path = parent ? adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent->context()))) : adoptGRef(gtk_widget_path_new());
if (!siblings.isEmpty()) {
GRefPtr<GtkWidgetPath> siblingsPath = adoptGRef(gtk_widget_path_new());
for (const auto& siblingInfo : siblings)
appendElementToPath(siblingsPath.get(), siblingInfo);
gtk_widget_path_append_with_siblings(path.get(), siblingsPath.get(), position);
} else
appendElementToPath(path.get(), info);
m_context = createStyleContext(path.get(), parent ? parent->context() : nullptr);
}
RenderThemeGadget::~RenderThemeGadget() = default;
GtkBorder RenderThemeGadget::marginBox() const
{
GtkBorder returnValue;
gtk_style_context_get_margin(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
return returnValue;
}
GtkBorder RenderThemeGadget::borderBox() const
{
GtkBorder returnValue;
gtk_style_context_get_border(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
return returnValue;
}
GtkBorder RenderThemeGadget::paddingBox() const
{
GtkBorder returnValue;
gtk_style_context_get_padding(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
return returnValue;
}
GtkBorder RenderThemeGadget::contentsBox() const
{
auto margin = marginBox();
auto border = borderBox();
auto padding = paddingBox();
padding.left += margin.left + border.left;
padding.right += margin.right + border.right;
padding.top += margin.top + border.top;
padding.bottom += margin.bottom + border.bottom;
return padding;
}
Color RenderThemeGadget::color() const
{
GdkRGBA returnValue;
gtk_style_context_get_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
return returnValue;
}
Color RenderThemeGadget::backgroundColor() const
{
GdkRGBA returnValue;
gtk_style_context_get_background_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
return returnValue;
}
double RenderThemeGadget::opacity() const
{
double returnValue;
gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "opacity", &returnValue, nullptr);
return returnValue;
}
GtkStateFlags RenderThemeGadget::state() const
{
return gtk_style_context_get_state(m_context.get());
}
void RenderThemeGadget::setState(GtkStateFlags state)
{
gtk_style_context_set_state(m_context.get(), state);
}
IntSize RenderThemeGadget::minimumSize() const
{
int width, height;
gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "min-width", &width, "min-height", &height, nullptr);
return IntSize(width, height);
}
IntSize RenderThemeGadget::preferredSize() const
{
auto margin = marginBox();
auto border = borderBox();
auto padding = paddingBox();
auto minSize = minimumSize();
minSize.expand(margin.left + margin.right + border.left + border.right + padding.left + padding.right,
margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom);
return minSize;
}
bool RenderThemeGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect* contentsRect)
{
FloatRect rect = paintRect;
auto margin = marginBox();
rect.move(margin.left, margin.top);
rect.contract(margin.left + margin.right, margin.top + margin.bottom);
auto minSize = minimumSize();
rect.setWidth(std::max<float>(rect.width(), minSize.width()));
rect.setHeight(std::max<float>(rect.height(), minSize.height()));
gtk_render_background(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
gtk_render_frame(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
if (contentsRect) {
auto border = borderBox();
auto padding = paddingBox();
*contentsRect = rect;
contentsRect->move(border.left + padding.left, border.top + padding.top);
contentsRect->contract(border.left + border.right + padding.left + padding.right, border.top + border.bottom + padding.top + padding.bottom);
}
return true;
}
void RenderThemeGadget::renderFocus(cairo_t* cr, const FloatRect& focusRect)
{
FloatRect rect = focusRect;
auto margin = marginBox();
rect.move(margin.left, margin.top);
rect.contract(margin.left + margin.right, margin.top + margin.bottom);
gtk_render_focus(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
}
RenderThemeBoxGadget::RenderThemeBoxGadget(const RenderThemeGadget::Info& info, GtkOrientation orientation, const Vector<RenderThemeGadget::Info> children, RenderThemeGadget* parent)
: RenderThemeGadget(info, parent, Vector<RenderThemeGadget::Info>(), 0)
, m_orientation(orientation)
{
m_children.reserveCapacity(children.size());
unsigned index = 0;
for (const auto& childInfo : children)
m_children.uncheckedAppend(RenderThemeGadget::create(childInfo, this, children, index++));
}
IntSize RenderThemeBoxGadget::preferredSize() const
{
IntSize childrenSize;
for (const auto& child : m_children) {
IntSize childSize = child->preferredSize();
switch (m_orientation) {
case GTK_ORIENTATION_HORIZONTAL:
childrenSize.setWidth(childrenSize.width() + childSize.width());
childrenSize.setHeight(std::max(childrenSize.height(), childSize.height()));
break;
case GTK_ORIENTATION_VERTICAL:
childrenSize.setWidth(std::max(childrenSize.width(), childSize.width()));
childrenSize.setHeight(childrenSize.height() + childSize.height());
break;
}
}
return RenderThemeGadget::preferredSize().expandedTo(childrenSize);
}
RenderThemeTextFieldGadget::RenderThemeTextFieldGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
{
}
IntSize RenderThemeTextFieldGadget::minimumSize() const
{
// We allow text fields smaller than the min size set on themes.
return IntSize();
}
RenderThemeToggleGadget::RenderThemeToggleGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
, m_type(info.type)
{
ASSERT(m_type == RenderThemeGadget::Type::Radio || m_type == RenderThemeGadget::Type::Check);
}
bool RenderThemeToggleGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
{
FloatRect contentsRect;
RenderThemeGadget::render(cr, paintRect, &contentsRect);
if (m_type == RenderThemeGadget::Type::Radio)
gtk_render_option(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
else
gtk_render_check(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
return true;
}
RenderThemeArrowGadget::RenderThemeArrowGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
{
}
bool RenderThemeArrowGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
{
FloatRect contentsRect;
RenderThemeGadget::render(cr, paintRect, &contentsRect);
IntSize minSize = minimumSize();
int arrowSize = std::min(minSize.width(), minSize.height());
FloatPoint arrowPosition(contentsRect.x(), contentsRect.y() + (contentsRect.height() - arrowSize) / 2);
if (gtk_style_context_get_state(m_context.get()) & GTK_STATE_FLAG_DIR_LTR)
arrowPosition.move(contentsRect.width() - arrowSize, 0);
gtk_render_arrow(m_context.get(), cr, G_PI / 2, arrowPosition.x(), arrowPosition.y(), arrowSize);
return true;
}
RenderThemeIconGadget::RenderThemeIconGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
{
}
GtkIconSize RenderThemeIconGadget::gtkIconSizeForPixelSize(unsigned pixelSize) const
{
if (pixelSize < IconSizeGtk::SmallToolbar)
return GTK_ICON_SIZE_MENU;
if (pixelSize >= IconSizeGtk::SmallToolbar && pixelSize < IconSizeGtk::Button)
return GTK_ICON_SIZE_SMALL_TOOLBAR;
if (pixelSize >= IconSizeGtk::Button && pixelSize < IconSizeGtk::LargeToolbar)
return GTK_ICON_SIZE_BUTTON;
if (pixelSize >= IconSizeGtk::LargeToolbar && pixelSize < IconSizeGtk::DragAndDrop)
return GTK_ICON_SIZE_LARGE_TOOLBAR;
if (pixelSize >= IconSizeGtk::DragAndDrop && pixelSize < IconSizeGtk::Dialog)
return GTK_ICON_SIZE_DND;
return GTK_ICON_SIZE_DIALOG;
}
bool RenderThemeIconGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
{
ASSERT(!m_iconName.isNull());
GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(m_iconName.data()));
unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
GtkTextDirection direction = gtk_style_context_get_direction(m_context.get());
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;
int iconWidth, iconHeight;
if (!gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
iconWidth = iconHeight = m_iconSize;
GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(),
std::min(iconWidth, iconHeight), static_cast<GtkIconLookupFlags>(lookupFlags)));
if (!iconInfo)
return false;
GRefPtr<GdkPixbuf> iconPixbuf = adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), m_context.get(), nullptr, nullptr));
if (!iconPixbuf)
return false;
FloatSize pixbufSize(gdk_pixbuf_get_width(iconPixbuf.get()), gdk_pixbuf_get_height(iconPixbuf.get()));
FloatRect contentsRect;
RenderThemeGadget::render(cr, paintRect, &contentsRect);
if (pixbufSize.width() > contentsRect.width() || pixbufSize.height() > contentsRect.height()) {
iconWidth = iconHeight = std::min(contentsRect.width(), contentsRect.height());
pixbufSize = FloatSize(iconWidth, iconHeight);
iconPixbuf = adoptGRef(gdk_pixbuf_scale_simple(iconPixbuf.get(), pixbufSize.width(), pixbufSize.height(), GDK_INTERP_BILINEAR));
}
gtk_render_icon(m_context.get(), cr, iconPixbuf.get(), contentsRect.x() + (contentsRect.width() - pixbufSize.width()) / 2,
contentsRect.y() + (contentsRect.height() - pixbufSize.height()) / 2);
return true;
}
IntSize RenderThemeIconGadget::minimumSize() const
{
if (m_iconSize < IconSizeGtk::Menu)
return IntSize(m_iconSize, m_iconSize);
int iconWidth, iconHeight;
if (gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
return IntSize(iconWidth, iconHeight);
return IntSize(m_iconSize, m_iconSize);
}
RenderThemeScrollbarGadget::RenderThemeScrollbarGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
{
gboolean hasBackward, hasForward, hasSecondaryBackward, hasSecondaryForward;
gtk_style_context_get_style(m_context.get(), "has-backward-stepper", &hasBackward, "has-forward-stepper", &hasForward,
"has-secondary-backward-stepper", &hasSecondaryBackward, "has-secondary-forward-stepper", &hasSecondaryForward, nullptr);
if (hasBackward)
m_steppers.add(Steppers::Backward);
if (hasForward)
m_steppers.add(Steppers::Forward);
if (hasSecondaryBackward)
m_steppers.add(Steppers::SecondaryBackward);
if (hasSecondaryForward)
m_steppers.add(Steppers::SecondaryForward);
}
void RenderThemeScrollbarGadget::renderStepper(cairo_t* cr, const FloatRect& paintRect, RenderThemeGadget* stepperGadget, GtkOrientation orientation, Steppers stepper)
{
FloatRect contentsRect;
stepperGadget->render(cr, paintRect, &contentsRect);
double angle;
switch (stepper) {
case Steppers::Backward:
case Steppers::SecondaryBackward:
angle = orientation == GTK_ORIENTATION_VERTICAL ? 0 : 3 * (G_PI / 2);
break;
case Steppers::Forward:
case Steppers::SecondaryForward:
angle = orientation == GTK_ORIENTATION_VERTICAL ? G_PI / 2 : G_PI;
break;
}
int stepperSize = std::max(contentsRect.width(), contentsRect.height());
gtk_render_arrow(stepperGadget->context(), cr, angle, contentsRect.x() + (contentsRect.width() - stepperSize) / 2,
contentsRect.y() + (contentsRect.height() - stepperSize) / 2, stepperSize);
}
RenderThemeButtonGadget::RenderThemeButtonGadget(const Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
: RenderThemeGadget(info, parent, siblings, position)
{
}
IntSize RenderThemeButtonGadget::minimumSize() const
{
// Allow buttons to be smaller than the minimum size
return IntSize();
}
} // namespace WebCore