1/*
2 * Copyright (C) 2016 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "RenderThemeGadget.h"
28
29#if GTK_CHECK_VERSION(3, 20, 0)
30
31#include "FloatRect.h"
32#include "GRefPtrGtk.h"
33
34namespace WebCore {
35
36std::unique_ptr<RenderThemeGadget> RenderThemeGadget::create(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
37{
38 switch (info.type) {
39 case RenderThemeGadget::Type::Generic:
40 return std::make_unique<RenderThemeGadget>(info, parent, siblings, position);
41 case RenderThemeGadget::Type::TextField:
42 return std::make_unique<RenderThemeTextFieldGadget>(info, parent, siblings, position);
43 case RenderThemeGadget::Type::Radio:
44 case RenderThemeGadget::Type::Check:
45 return std::make_unique<RenderThemeToggleGadget>(info, parent, siblings, position);
46 case RenderThemeGadget::Type::Arrow:
47 return std::make_unique<RenderThemeArrowGadget>(info, parent, siblings, position);
48 case RenderThemeGadget::Type::Icon:
49 return std::make_unique<RenderThemeIconGadget>(info, parent, siblings, position);
50 case RenderThemeGadget::Type::Scrollbar:
51 return std::make_unique<RenderThemeScrollbarGadget>(info, parent, siblings, position);
52 case RenderThemeGadget::Type::Button:
53 return std::make_unique<RenderThemeButtonGadget>(info, parent, siblings, position);
54 }
55
56 ASSERT_NOT_REACHED();
57 return nullptr;
58}
59
60static GRefPtr<GtkStyleContext> createStyleContext(GtkWidgetPath* path, GtkStyleContext* parent)
61{
62 GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
63 gtk_style_context_set_path(context.get(), path);
64 gtk_style_context_set_parent(context.get(), parent);
65 return context;
66}
67
68static void appendElementToPath(GtkWidgetPath* path, const RenderThemeGadget::Info& info)
69{
70 // Scrollbars need to use its GType to be able to get non-CSS style properties.
71 gtk_widget_path_append_type(path, info.type == RenderThemeGadget::Type::Scrollbar ? GTK_TYPE_SCROLLBAR : G_TYPE_NONE);
72 gtk_widget_path_iter_set_object_name(path, -1, info.name);
73 for (const auto* className : info.classList)
74 gtk_widget_path_iter_add_class(path, -1, className);
75}
76
77RenderThemeGadget::RenderThemeGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
78{
79 GRefPtr<GtkWidgetPath> path = parent ? adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent->context()))) : adoptGRef(gtk_widget_path_new());
80 if (!siblings.isEmpty()) {
81 GRefPtr<GtkWidgetPath> siblingsPath = adoptGRef(gtk_widget_path_new());
82 for (const auto& siblingInfo : siblings)
83 appendElementToPath(siblingsPath.get(), siblingInfo);
84 gtk_widget_path_append_with_siblings(path.get(), siblingsPath.get(), position);
85 } else
86 appendElementToPath(path.get(), info);
87 m_context = createStyleContext(path.get(), parent ? parent->context() : nullptr);
88}
89
90RenderThemeGadget::~RenderThemeGadget() = default;
91
92GtkBorder RenderThemeGadget::marginBox() const
93{
94 GtkBorder returnValue;
95 gtk_style_context_get_margin(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
96 return returnValue;
97}
98
99GtkBorder RenderThemeGadget::borderBox() const
100{
101 GtkBorder returnValue;
102 gtk_style_context_get_border(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
103 return returnValue;
104}
105
106GtkBorder RenderThemeGadget::paddingBox() const
107{
108 GtkBorder returnValue;
109 gtk_style_context_get_padding(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
110 return returnValue;
111}
112
113GtkBorder RenderThemeGadget::contentsBox() const
114{
115 auto margin = marginBox();
116 auto border = borderBox();
117 auto padding = paddingBox();
118 padding.left += margin.left + border.left;
119 padding.right += margin.right + border.right;
120 padding.top += margin.top + border.top;
121 padding.bottom += margin.bottom + border.bottom;
122 return padding;
123}
124
125Color RenderThemeGadget::color() const
126{
127 GdkRGBA returnValue;
128 gtk_style_context_get_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
129 return returnValue;
130}
131
132Color RenderThemeGadget::backgroundColor() const
133{
134 GdkRGBA returnValue;
135 gtk_style_context_get_background_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
136 return returnValue;
137}
138
139double RenderThemeGadget::opacity() const
140{
141 double returnValue;
142 gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "opacity", &returnValue, nullptr);
143 return returnValue;
144}
145
146GtkStateFlags RenderThemeGadget::state() const
147{
148 return gtk_style_context_get_state(m_context.get());
149}
150
151void RenderThemeGadget::setState(GtkStateFlags state)
152{
153 gtk_style_context_set_state(m_context.get(), state);
154}
155
156IntSize RenderThemeGadget::minimumSize() const
157{
158 int width, height;
159 gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "min-width", &width, "min-height", &height, nullptr);
160 return IntSize(width, height);
161}
162
163IntSize RenderThemeGadget::preferredSize() const
164{
165 auto margin = marginBox();
166 auto border = borderBox();
167 auto padding = paddingBox();
168 auto minSize = minimumSize();
169 minSize.expand(margin.left + margin.right + border.left + border.right + padding.left + padding.right,
170 margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom);
171 return minSize;
172}
173
174bool RenderThemeGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect* contentsRect)
175{
176 FloatRect rect = paintRect;
177
178 auto margin = marginBox();
179 rect.move(margin.left, margin.top);
180 rect.contract(margin.left + margin.right, margin.top + margin.bottom);
181
182 auto minSize = minimumSize();
183 rect.setWidth(std::max<float>(rect.width(), minSize.width()));
184 rect.setHeight(std::max<float>(rect.height(), minSize.height()));
185
186 gtk_render_background(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
187 gtk_render_frame(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
188
189 if (contentsRect) {
190 auto border = borderBox();
191 auto padding = paddingBox();
192 *contentsRect = rect;
193 contentsRect->move(border.left + padding.left, border.top + padding.top);
194 contentsRect->contract(border.left + border.right + padding.left + padding.right, border.top + border.bottom + padding.top + padding.bottom);
195 }
196
197 return true;
198}
199
200void RenderThemeGadget::renderFocus(cairo_t* cr, const FloatRect& focusRect)
201{
202 FloatRect rect = focusRect;
203 auto margin = marginBox();
204 rect.move(margin.left, margin.top);
205 rect.contract(margin.left + margin.right, margin.top + margin.bottom);
206 gtk_render_focus(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
207}
208
209RenderThemeBoxGadget::RenderThemeBoxGadget(const RenderThemeGadget::Info& info, GtkOrientation orientation, const Vector<RenderThemeGadget::Info> children, RenderThemeGadget* parent)
210 : RenderThemeGadget(info, parent, Vector<RenderThemeGadget::Info>(), 0)
211 , m_orientation(orientation)
212{
213 m_children.reserveCapacity(children.size());
214 unsigned index = 0;
215 for (const auto& childInfo : children)
216 m_children.uncheckedAppend(RenderThemeGadget::create(childInfo, this, children, index++));
217}
218
219IntSize RenderThemeBoxGadget::preferredSize() const
220{
221 IntSize childrenSize;
222 for (const auto& child : m_children) {
223 IntSize childSize = child->preferredSize();
224 switch (m_orientation) {
225 case GTK_ORIENTATION_HORIZONTAL:
226 childrenSize.setWidth(childrenSize.width() + childSize.width());
227 childrenSize.setHeight(std::max(childrenSize.height(), childSize.height()));
228 break;
229 case GTK_ORIENTATION_VERTICAL:
230 childrenSize.setWidth(std::max(childrenSize.width(), childSize.width()));
231 childrenSize.setHeight(childrenSize.height() + childSize.height());
232 break;
233 }
234 }
235 return RenderThemeGadget::preferredSize().expandedTo(childrenSize);
236}
237
238RenderThemeTextFieldGadget::RenderThemeTextFieldGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
239 : RenderThemeGadget(info, parent, siblings, position)
240{
241}
242
243IntSize RenderThemeTextFieldGadget::minimumSize() const
244{
245 // We allow text fields smaller than the min size set on themes.
246 return IntSize();
247}
248
249RenderThemeToggleGadget::RenderThemeToggleGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
250 : RenderThemeGadget(info, parent, siblings, position)
251 , m_type(info.type)
252{
253 ASSERT(m_type == RenderThemeGadget::Type::Radio || m_type == RenderThemeGadget::Type::Check);
254}
255
256bool RenderThemeToggleGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
257{
258 FloatRect contentsRect;
259 RenderThemeGadget::render(cr, paintRect, &contentsRect);
260 if (m_type == RenderThemeGadget::Type::Radio)
261 gtk_render_option(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
262 else
263 gtk_render_check(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
264 return true;
265}
266
267RenderThemeArrowGadget::RenderThemeArrowGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
268 : RenderThemeGadget(info, parent, siblings, position)
269{
270}
271
272bool RenderThemeArrowGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
273{
274 FloatRect contentsRect;
275 RenderThemeGadget::render(cr, paintRect, &contentsRect);
276 IntSize minSize = minimumSize();
277 int arrowSize = std::min(minSize.width(), minSize.height());
278 FloatPoint arrowPosition(contentsRect.x(), contentsRect.y() + (contentsRect.height() - arrowSize) / 2);
279 if (gtk_style_context_get_state(m_context.get()) & GTK_STATE_FLAG_DIR_LTR)
280 arrowPosition.move(contentsRect.width() - arrowSize, 0);
281 gtk_render_arrow(m_context.get(), cr, G_PI / 2, arrowPosition.x(), arrowPosition.y(), arrowSize);
282 return true;
283}
284
285RenderThemeIconGadget::RenderThemeIconGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
286 : RenderThemeGadget(info, parent, siblings, position)
287{
288}
289
290GtkIconSize RenderThemeIconGadget::gtkIconSizeForPixelSize(unsigned pixelSize) const
291{
292 if (pixelSize < IconSizeGtk::SmallToolbar)
293 return GTK_ICON_SIZE_MENU;
294 if (pixelSize >= IconSizeGtk::SmallToolbar && pixelSize < IconSizeGtk::Button)
295 return GTK_ICON_SIZE_SMALL_TOOLBAR;
296 if (pixelSize >= IconSizeGtk::Button && pixelSize < IconSizeGtk::LargeToolbar)
297 return GTK_ICON_SIZE_BUTTON;
298 if (pixelSize >= IconSizeGtk::LargeToolbar && pixelSize < IconSizeGtk::DragAndDrop)
299 return GTK_ICON_SIZE_LARGE_TOOLBAR;
300 if (pixelSize >= IconSizeGtk::DragAndDrop && pixelSize < IconSizeGtk::Dialog)
301 return GTK_ICON_SIZE_DND;
302
303 return GTK_ICON_SIZE_DIALOG;
304}
305
306bool RenderThemeIconGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
307{
308 ASSERT(!m_iconName.isNull());
309 GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(m_iconName.data()));
310 unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
311 GtkTextDirection direction = gtk_style_context_get_direction(m_context.get());
312 if (direction & GTK_TEXT_DIR_LTR)
313 lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
314 else if (direction & GTK_TEXT_DIR_RTL)
315 lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
316 int iconWidth, iconHeight;
317 if (!gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
318 iconWidth = iconHeight = m_iconSize;
319 GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(),
320 std::min(iconWidth, iconHeight), static_cast<GtkIconLookupFlags>(lookupFlags)));
321 if (!iconInfo)
322 return false;
323
324 GRefPtr<GdkPixbuf> iconPixbuf = adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), m_context.get(), nullptr, nullptr));
325 if (!iconPixbuf)
326 return false;
327
328 FloatSize pixbufSize(gdk_pixbuf_get_width(iconPixbuf.get()), gdk_pixbuf_get_height(iconPixbuf.get()));
329 FloatRect contentsRect;
330 RenderThemeGadget::render(cr, paintRect, &contentsRect);
331 if (pixbufSize.width() > contentsRect.width() || pixbufSize.height() > contentsRect.height()) {
332 iconWidth = iconHeight = std::min(contentsRect.width(), contentsRect.height());
333 pixbufSize = FloatSize(iconWidth, iconHeight);
334 iconPixbuf = adoptGRef(gdk_pixbuf_scale_simple(iconPixbuf.get(), pixbufSize.width(), pixbufSize.height(), GDK_INTERP_BILINEAR));
335 }
336
337 gtk_render_icon(m_context.get(), cr, iconPixbuf.get(), contentsRect.x() + (contentsRect.width() - pixbufSize.width()) / 2,
338 contentsRect.y() + (contentsRect.height() - pixbufSize.height()) / 2);
339 return true;
340}
341
342IntSize RenderThemeIconGadget::minimumSize() const
343{
344 if (m_iconSize < IconSizeGtk::Menu)
345 return IntSize(m_iconSize, m_iconSize);
346
347 int iconWidth, iconHeight;
348 if (gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
349 return IntSize(iconWidth, iconHeight);
350
351 return IntSize(m_iconSize, m_iconSize);
352}
353
354RenderThemeScrollbarGadget::RenderThemeScrollbarGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
355 : RenderThemeGadget(info, parent, siblings, position)
356{
357 gboolean hasBackward, hasForward, hasSecondaryBackward, hasSecondaryForward;
358 gtk_style_context_get_style(m_context.get(), "has-backward-stepper", &hasBackward, "has-forward-stepper", &hasForward,
359 "has-secondary-backward-stepper", &hasSecondaryBackward, "has-secondary-forward-stepper", &hasSecondaryForward, nullptr);
360 if (hasBackward)
361 m_steppers.add(Steppers::Backward);
362 if (hasForward)
363 m_steppers.add(Steppers::Forward);
364 if (hasSecondaryBackward)
365 m_steppers.add(Steppers::SecondaryBackward);
366 if (hasSecondaryForward)
367 m_steppers.add(Steppers::SecondaryForward);
368}
369
370void RenderThemeScrollbarGadget::renderStepper(cairo_t* cr, const FloatRect& paintRect, RenderThemeGadget* stepperGadget, GtkOrientation orientation, Steppers stepper)
371{
372 FloatRect contentsRect;
373 stepperGadget->render(cr, paintRect, &contentsRect);
374 double angle;
375 switch (stepper) {
376 case Steppers::Backward:
377 case Steppers::SecondaryBackward:
378 angle = orientation == GTK_ORIENTATION_VERTICAL ? 0 : 3 * (G_PI / 2);
379 break;
380 case Steppers::Forward:
381 case Steppers::SecondaryForward:
382 angle = orientation == GTK_ORIENTATION_VERTICAL ? G_PI / 2 : G_PI;
383 break;
384 }
385
386 int stepperSize = std::max(contentsRect.width(), contentsRect.height());
387 gtk_render_arrow(stepperGadget->context(), cr, angle, contentsRect.x() + (contentsRect.width() - stepperSize) / 2,
388 contentsRect.y() + (contentsRect.height() - stepperSize) / 2, stepperSize);
389}
390
391RenderThemeButtonGadget::RenderThemeButtonGadget(const Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
392 : RenderThemeGadget(info, parent, siblings, position)
393{
394}
395
396IntSize RenderThemeButtonGadget::minimumSize() const
397{
398 // Allow buttons to be smaller than the minimum size
399 return IntSize();
400}
401
402} // namespace WebCore
403
404#endif // GTK_CHECK_VERSION(3, 20, 0)
405