3e6af49c1b67890275df51eb59d073fa2f33541a
[WebKit-https.git] / Source / WebCore / platform / gtk / ScrollbarThemeGtk.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "ScrollbarThemeGtk.h"
29
30 #include "GRefPtrGtk.h"
31 #include "PlatformContextCairo.h"
32 #include "PlatformMouseEvent.h"
33 #include "RenderThemeWidget.h"
34 #include "ScrollView.h"
35 #include "Scrollbar.h"
36 #include <cstdlib>
37 #include <gtk/gtk.h>
38
39 namespace WebCore {
40
41 ScrollbarTheme& ScrollbarTheme::nativeTheme()
42 {
43     static ScrollbarThemeGtk theme;
44     return theme;
45 }
46
47 ScrollbarThemeGtk::~ScrollbarThemeGtk() = default;
48
49 static void themeChangedCallback()
50 {
51     ScrollbarTheme::theme().themeChanged();
52 }
53
54 ScrollbarThemeGtk::ScrollbarThemeGtk()
55 {
56 #if GTK_CHECK_VERSION(3, 20, 0)
57     m_usesOverlayScrollbars = g_strcmp0(g_getenv("GTK_OVERLAY_SCROLLING"), "0");
58 #endif
59     static bool themeMonitorInitialized = false;
60     if (!themeMonitorInitialized) {
61         g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
62         themeMonitorInitialized = true;
63         updateThemeProperties();
64     }
65 }
66
67 #if !GTK_CHECK_VERSION(3, 20, 0)
68 static GRefPtr<GtkStyleContext> createStyleContext(Scrollbar* scrollbar = nullptr)
69 {
70     GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
71     GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_new());
72     gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
73     gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
74     gtk_widget_path_iter_add_class(path.get(), -1, scrollbar && scrollbar->orientation() == HorizontalScrollbar ? "horizontal" : "vertical");
75     gtk_style_context_set_path(styleContext.get(), path.get());
76     return styleContext;
77 }
78
79 static GRefPtr<GtkStyleContext> createChildStyleContext(GtkStyleContext* parent, const char* className)
80 {
81     ASSERT(parent);
82     GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent)));
83     gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
84     gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
85     gtk_widget_path_iter_add_class(path.get(), -1, className);
86
87     GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
88     gtk_style_context_set_path(styleContext.get(), path.get());
89     gtk_style_context_set_parent(styleContext.get(), parent);
90     return styleContext;
91 }
92 #endif // GTK_CHECK_VERSION(3, 20, 0)
93
94 void ScrollbarThemeGtk::themeChanged()
95 {
96 #if GTK_CHECK_VERSION(3, 20, 0)
97     RenderThemeWidget::clearCache();
98 #endif
99     updateThemeProperties();
100 }
101
102 #if GTK_CHECK_VERSION(3, 20, 0)
103 void ScrollbarThemeGtk::updateThemeProperties()
104 {
105     auto& scrollbar = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
106     m_hasBackButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
107     m_hasForwardButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
108     m_hasBackButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
109     m_hasForwardButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
110 }
111 #else
112 void ScrollbarThemeGtk::updateThemeProperties()
113 {
114     gboolean hasBackButtonStartPart, hasForwardButtonEndPart, hasBackButtonEndPart, hasForwardButtonStartPart;
115     gtk_style_context_get_style(createStyleContext().get(),
116         "has-backward-stepper", &hasBackButtonStartPart,
117         "has-forward-stepper", &hasForwardButtonEndPart,
118         "has-secondary-backward-stepper", &hasBackButtonEndPart,
119         "has-secondary-forward-stepper", &hasForwardButtonStartPart,
120         nullptr);
121     m_hasBackButtonStartPart = hasBackButtonStartPart;
122     m_hasForwardButtonEndPart = hasForwardButtonEndPart;
123     m_hasBackButtonEndPart = hasBackButtonEndPart;
124     m_hasForwardButtonStartPart = hasForwardButtonStartPart;
125 }
126 #endif // GTK_CHECK_VERSION(3, 20, 0)
127
128 bool ScrollbarThemeGtk::hasButtons(Scrollbar& scrollbar)
129 {
130     return scrollbar.enabled() && (m_hasBackButtonStartPart || m_hasForwardButtonEndPart || m_hasBackButtonEndPart || m_hasForwardButtonStartPart);
131 }
132
133 #if GTK_CHECK_VERSION(3, 20, 0)
134 static GtkStateFlags scrollbarPartStateFlags(Scrollbar& scrollbar, ScrollbarPart part, bool painting = false)
135 {
136     unsigned stateFlags = 0;
137     switch (part) {
138     case AllParts:
139         if (!painting || scrollbar.hoveredPart() != NoPart)
140             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
141         break;
142     case BackTrackPart:
143     case ForwardTrackPart:
144         if (scrollbar.hoveredPart() == BackTrackPart || scrollbar.hoveredPart() == ForwardTrackPart)
145             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
146         if (scrollbar.pressedPart() == BackTrackPart || scrollbar.pressedPart() == ForwardTrackPart)
147             stateFlags |= GTK_STATE_FLAG_ACTIVE;
148         break;
149     case BackButtonStartPart:
150     case ForwardButtonStartPart:
151     case BackButtonEndPart:
152     case ForwardButtonEndPart:
153         if (((part == BackButtonStartPart || part == BackButtonEndPart) && !scrollbar.currentPos())
154             || ((part == ForwardButtonEndPart || part == ForwardButtonStartPart) && scrollbar.currentPos() == scrollbar.maximum())) {
155             stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
156             break;
157         }
158         FALLTHROUGH;
159     default:
160         if (scrollbar.hoveredPart() == part)
161             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
162
163         if (scrollbar.pressedPart() == part)
164             stateFlags |= GTK_STATE_FLAG_ACTIVE;
165         break;
166     }
167
168     return static_cast<GtkStateFlags>(stateFlags);
169 }
170
171 static RenderThemeWidget::Type widgetTypeForScrollbar(Scrollbar& scrollbar, GtkStateFlags scrollbarState)
172 {
173     if (scrollbar.orientation() == VerticalScrollbar) {
174         if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
175             return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarLeft : RenderThemeWidget::Type::VerticalScrollIndicatorLeft;
176         return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarRight : RenderThemeWidget::Type::VerticalScrollIndicatorRight;
177     }
178     return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::HorizontalScrollbar : RenderThemeWidget::Type::HorizontalScrollIndicator;
179 }
180
181 static IntRect contentsRectangle(Scrollbar& scrollbar, RenderThemeScrollbar& scrollbarWidget)
182 {
183     GtkBorder scrollbarContentsBox = scrollbarWidget.scrollbar().contentsBox();
184     GtkBorder contentsContentsBox = scrollbarWidget.contents().contentsBox();
185     GtkBorder padding;
186     padding.left = scrollbarContentsBox.left + contentsContentsBox.left;
187     padding.right = scrollbarContentsBox.right + contentsContentsBox.right;
188     padding.top = scrollbarContentsBox.top + contentsContentsBox.top;
189     padding.bottom = scrollbarContentsBox.bottom + contentsContentsBox.bottom;
190     IntRect contentsRect = scrollbar.frameRect();
191     contentsRect.move(padding.left, padding.top);
192     contentsRect.contract(padding.left + padding.right, padding.top + padding.bottom);
193     return contentsRect;
194 }
195
196 IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
197 {
198     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
199     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
200     scrollbarWidget.scrollbar().setState(scrollbarState);
201
202     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
203     if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
204         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
205         IntSize stepperSize = backwardStepper->preferredSize();
206         if (scrollbar.orientation() == VerticalScrollbar) {
207             rect.move(0, stepperSize.height());
208             rect.contract(0, stepperSize.height());
209         } else {
210             rect.move(stepperSize.width(), 0);
211             rect.contract(stepperSize.width(), 0);
212         }
213     }
214     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
215         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
216         IntSize stepperSize = secondaryForwardStepper->preferredSize();
217         if (scrollbar.orientation() == VerticalScrollbar) {
218             rect.move(0, stepperSize.height());
219             rect.contract(0, stepperSize.height());
220         } else {
221             rect.move(stepperSize.width(), 0);
222             rect.contract(stepperSize.width(), 0);
223         }
224     }
225     if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
226         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
227         if (scrollbar.orientation() == VerticalScrollbar)
228             rect.contract(0, secondaryBackwardStepper->preferredSize().height());
229         else
230             rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
231     }
232     if (auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward)) {
233         forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
234         if (scrollbar.orientation() == VerticalScrollbar)
235             rect.contract(0, forwardStepper->preferredSize().height());
236         else
237             rect.contract(forwardStepper->preferredSize().width(), 0);
238     }
239
240     if (scrollbar.orientation() == VerticalScrollbar)
241         return scrollbar.height() < rect.height() ? IntRect() : rect;
242
243     return scrollbar.width() < rect.width() ? IntRect() : rect;
244 }
245 #else
246 IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
247 {
248     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
249     // The padding along the thumb movement axis includes the trough border
250     // plus the size of stepper spacing (the space between the stepper and
251     // the place where the thumb stops). There is often no stepper spacing.
252     int stepperSpacing, stepperSize, troughBorderWidth, thumbFat;
253     gtk_style_context_get_style(styleContext.get(), "stepper-spacing", &stepperSpacing, "stepper-size", &stepperSize, "trough-border",
254         &troughBorderWidth, "slider-width", &thumbFat, nullptr);
255
256     // The fatness of the scrollbar on the non-movement axis.
257     int thickness = thumbFat + 2 * troughBorderWidth;
258
259     int startButtonsOffset = 0;
260     int buttonsWidth = 0;
261     if (m_hasForwardButtonStartPart) {
262         startButtonsOffset += stepperSize;
263         buttonsWidth += stepperSize;
264     }
265     if (m_hasBackButtonStartPart) {
266         startButtonsOffset += stepperSize;
267         buttonsWidth += stepperSize;
268     }
269     if (m_hasBackButtonEndPart)
270         buttonsWidth += stepperSize;
271     if (m_hasForwardButtonEndPart)
272         buttonsWidth += stepperSize;
273
274     if (scrollbar.orientation() == HorizontalScrollbar) {
275         // Once the scrollbar becomes smaller than the natural size of the two buttons and the thumb, the track disappears.
276         if (scrollbar.width() < buttonsWidth + minimumThumbLength(scrollbar))
277             return IntRect();
278         return IntRect(scrollbar.x() + troughBorderWidth + stepperSpacing + startButtonsOffset, scrollbar.y(),
279             scrollbar.width() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth, thickness);
280     }
281
282     if (scrollbar.height() < buttonsWidth + minimumThumbLength(scrollbar))
283         return IntRect();
284     return IntRect(scrollbar.x(), scrollbar.y() + troughBorderWidth + stepperSpacing + startButtonsOffset,
285         thickness, scrollbar.height() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth);
286 }
287 #endif
288
289 bool ScrollbarThemeGtk::hasThumb(Scrollbar& scrollbar)
290 {
291     // This method is just called as a paint-time optimization to see if
292     // painting the thumb can be skipped. We don't have to be exact here.
293     return thumbLength(scrollbar) > 0;
294 }
295
296 #if GTK_CHECK_VERSION(3, 20, 0)
297 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
298 {
299     ASSERT(part == BackButtonStartPart || part == BackButtonEndPart);
300     if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
301         return IntRect();
302
303     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
304     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
305     scrollbarWidget.scrollbar().setState(scrollbarState);
306
307     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
308     if (part == BackButtonStartPart) {
309         auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
310         ASSERT(backwardStepper);
311         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
312         return IntRect(rect.location(), backwardStepper->preferredSize());
313     }
314
315     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
316         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
317         IntSize preferredSize = secondaryForwardStepper->preferredSize();
318         if (scrollbar.orientation() == VerticalScrollbar) {
319             rect.move(0, preferredSize.height());
320             rect.contract(0, preferredSize.height());
321         } else {
322             rect.move(preferredSize.width(), 0);
323             rect.contract(0, preferredSize.width());
324         }
325     }
326
327     if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
328         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
329         if (scrollbar.orientation() == VerticalScrollbar)
330             rect.contract(0, secondaryBackwardStepper->preferredSize().height());
331         else
332             rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
333     }
334
335     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
336     ASSERT(forwardStepper);
337     forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
338     IntSize preferredSize = forwardStepper->preferredSize();
339     if (scrollbar.orientation() == VerticalScrollbar)
340         rect.move(0, rect.height() - preferredSize.height());
341     else
342         rect.move(rect.width() - preferredSize.width(), 0);
343
344     return IntRect(rect.location(), preferredSize);
345 }
346
347 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
348 {
349     ASSERT(part == ForwardButtonStartPart || part == ForwardButtonEndPart);
350     if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
351         return IntRect();
352
353     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
354     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
355     scrollbarWidget.scrollbar().setState(scrollbarState);
356
357     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
358     if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
359         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
360         IntSize preferredSize = backwardStepper->preferredSize();
361         if (scrollbar.orientation() == VerticalScrollbar) {
362             rect.move(0, preferredSize.height());
363             rect.contract(0, preferredSize.height());
364         } else {
365             rect.move(preferredSize.width(), 0);
366             rect.contract(preferredSize.width(), 0);
367         }
368     }
369
370     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
371         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
372         IntSize preferredSize = secondaryForwardStepper->preferredSize();
373         if (part == ForwardButtonStartPart)
374             return IntRect(rect.location(), preferredSize);
375
376         if (scrollbar.orientation() == VerticalScrollbar) {
377             rect.move(0, preferredSize.height());
378             rect.contract(0, preferredSize.height());
379         } else {
380             rect.move(preferredSize.width(), 0);
381             rect.contract(preferredSize.width(), 0);
382         }
383     }
384
385     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
386     ASSERT(forwardStepper);
387     forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
388     IntSize preferredSize = forwardStepper->preferredSize();
389     if (scrollbar.orientation() == VerticalScrollbar)
390         rect.move(0, rect.height() - preferredSize.height());
391     else
392         rect.move(rect.width() - preferredSize.width(), 0);
393
394     return IntRect(rect.location(), preferredSize);
395 }
396 #else
397 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
398 {
399     if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
400         return IntRect();
401
402     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
403     int troughBorderWidth, stepperSize, thumbFat;
404     gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
405     int x = scrollbar.x() + troughBorderWidth;
406     int y = scrollbar.y() + troughBorderWidth;
407     if (part == BackButtonStartPart) {
408         if (scrollbar.orientation() == HorizontalScrollbar)
409             return IntRect(x, y, stepperSize, thumbFat);
410         return IntRect(x, y, thumbFat, stepperSize);
411     }
412
413     // BackButtonEndPart (alternate button)
414     if (scrollbar.orientation() == HorizontalScrollbar)
415         return IntRect(scrollbar.x() + scrollbar.width() - troughBorderWidth - (2 * stepperSize), y, stepperSize, thumbFat);
416
417     // VerticalScrollbar alternate button
418     return IntRect(x, scrollbar.y() + scrollbar.height() - troughBorderWidth - (2 * stepperSize), thumbFat, stepperSize);
419 }
420
421 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
422 {
423     if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
424         return IntRect();
425
426     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
427     int troughBorderWidth, stepperSize, thumbFat;
428     gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
429     if (scrollbar.orientation() == HorizontalScrollbar) {
430         int y = scrollbar.y() + troughBorderWidth;
431         if (part == ForwardButtonEndPart)
432             return IntRect(scrollbar.x() + scrollbar.width() - stepperSize - troughBorderWidth, y, stepperSize, thumbFat);
433
434         // ForwardButtonStartPart (alternate button)
435         return IntRect(scrollbar.x() + troughBorderWidth + stepperSize, y, stepperSize, thumbFat);
436     }
437
438     // VerticalScrollbar
439     int x = scrollbar.x() + troughBorderWidth;
440     if (part == ForwardButtonEndPart)
441         return IntRect(x, scrollbar.y() + scrollbar.height() - stepperSize - troughBorderWidth, thumbFat, stepperSize);
442
443     // ForwardButtonStartPart (alternate button)
444     return IntRect(x, scrollbar.y() + troughBorderWidth + stepperSize, thumbFat, stepperSize);
445 }
446 #endif // GTK_CHECK_VERSION(3, 20, 0)
447
448 #if GTK_CHECK_VERSION(3, 20, 0)
449 bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
450 {
451     if (graphicsContext.paintingDisabled())
452         return false;
453
454     if (!scrollbar.enabled())
455         return true;
456
457     double opacity = scrollbar.hoveredPart() == NoPart ? scrollbar.opacity() : 1;
458     if (!opacity)
459         return true;
460
461     IntRect rect = scrollbar.frameRect();
462     if (!rect.intersects(damageRect))
463         return true;
464
465     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts, true);
466     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
467     auto& scrollbarGadget = scrollbarWidget.scrollbar();
468     scrollbarGadget.setState(scrollbarState);
469     if (m_usesOverlayScrollbars)
470         opacity *= scrollbarGadget.opacity();
471     if (!opacity)
472         return true;
473
474     auto& trough = scrollbarWidget.trough();
475     trough.setState(scrollbarPartStateFlags(scrollbar, BackTrackPart));
476
477     auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
478     if (backwardStepper)
479         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
480     auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
481     if (secondaryForwardStepper)
482         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
483     auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
484     if (secondaryBackwardStepper)
485         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
486     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
487     if (forwardStepper)
488         forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
489
490     IntSize preferredSize = scrollbarWidget.contents().preferredSize();
491     int thumbSize = thumbLength(scrollbar);
492     if (thumbSize) {
493         scrollbarWidget.slider().setState(scrollbarPartStateFlags(scrollbar, ThumbPart));
494         preferredSize = preferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
495     }
496     preferredSize += scrollbarGadget.preferredSize() - scrollbarGadget.minimumSize();
497
498     FloatRect contentsRect(rect);
499     // When using overlay scrollbars we always claim the size of the scrollbar when hovered, so when
500     // drawing the indicator we need to adjust the rectangle to its actual size in indicator mode.
501     if (scrollbar.orientation() == VerticalScrollbar) {
502         if (rect.width() != preferredSize.width()) {
503             if (!scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
504                 contentsRect.move(std::abs(rect.width() - preferredSize.width()), 0);
505             contentsRect.setWidth(preferredSize.width());
506         }
507     } else {
508         if (rect.height() != preferredSize.height()) {
509             contentsRect.move(0, std::abs(rect.height() - preferredSize.height()));
510             contentsRect.setHeight(preferredSize.height());
511         }
512     }
513
514     if (opacity != 1) {
515         graphicsContext.save();
516         graphicsContext.clip(damageRect);
517         graphicsContext.beginTransparencyLayer(opacity);
518     }
519
520     scrollbarGadget.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
521     scrollbarWidget.contents().render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
522
523     if (backwardStepper) {
524         FloatRect buttonRect = contentsRect;
525         if (scrollbar.orientation() == VerticalScrollbar)
526             buttonRect.setHeight(backwardStepper->preferredSize().height());
527         else
528             buttonRect.setWidth(backwardStepper->preferredSize().width());
529         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, backwardStepper,
530             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Backward);
531         if (scrollbar.orientation() == VerticalScrollbar) {
532             contentsRect.move(0, buttonRect.height());
533             contentsRect.contract(0, buttonRect.height());
534         } else {
535             contentsRect.move(buttonRect.width(), 0);
536             contentsRect.contract(buttonRect.width(), 0);
537         }
538     }
539     if (secondaryForwardStepper) {
540         FloatRect buttonRect = contentsRect;
541         if (scrollbar.orientation() == VerticalScrollbar)
542             buttonRect.setHeight(secondaryForwardStepper->preferredSize().height());
543         else
544             buttonRect.setWidth(secondaryForwardStepper->preferredSize().width());
545         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryForwardStepper,
546             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryForward);
547         if (scrollbar.orientation() == VerticalScrollbar) {
548             contentsRect.move(0, buttonRect.height());
549             contentsRect.contract(0, buttonRect.height());
550         } else {
551             contentsRect.move(buttonRect.width(), 0);
552             contentsRect.contract(buttonRect.width(), 0);
553         }
554     }
555     if (secondaryBackwardStepper) {
556         FloatRect buttonRect = contentsRect;
557         if (scrollbar.orientation() == VerticalScrollbar) {
558             buttonRect.setHeight(secondaryBackwardStepper->preferredSize().height());
559             buttonRect.move(0, contentsRect.height() - buttonRect.height());
560         } else {
561             buttonRect.setWidth(secondaryBackwardStepper->preferredSize().width());
562             buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
563         }
564         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryBackwardStepper,
565             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
566         if (scrollbar.orientation() == VerticalScrollbar)
567             contentsRect.contract(0, buttonRect.height());
568         else
569             contentsRect.contract(buttonRect.width(), 0);
570     }
571     if (forwardStepper) {
572         FloatRect buttonRect = contentsRect;
573         if (scrollbar.orientation() == VerticalScrollbar) {
574             buttonRect.setHeight(forwardStepper->preferredSize().height());
575             buttonRect.move(0, contentsRect.height() - buttonRect.height());
576         } else {
577             buttonRect.setWidth(forwardStepper->preferredSize().width());
578             buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
579         }
580         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, forwardStepper,
581             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Forward);
582         if (scrollbar.orientation() == VerticalScrollbar)
583             contentsRect.contract(0, buttonRect.height());
584         else
585             contentsRect.contract(buttonRect.width(), 0);
586     }
587
588     trough.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
589
590     if (thumbSize) {
591         if (scrollbar.orientation() == VerticalScrollbar) {
592             contentsRect.move(0, thumbPosition(scrollbar));
593             contentsRect.setWidth(scrollbarWidget.slider().preferredSize().width());
594             contentsRect.setHeight(thumbSize);
595         } else {
596             contentsRect.move(thumbPosition(scrollbar), 0);
597             contentsRect.setWidth(thumbSize);
598             contentsRect.setHeight(scrollbarWidget.slider().preferredSize().height());
599         }
600         if (contentsRect.intersects(damageRect))
601             scrollbarWidget.slider().render(graphicsContext.platformContext()->cr(), contentsRect);
602     }
603
604     if (opacity != 1) {
605         graphicsContext.endTransparencyLayer();
606         graphicsContext.restore();
607     }
608
609     return true;
610 }
611 #else
612 static void paintStepper(GtkStyleContext* parentContext, GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part)
613 {
614     ScrollbarOrientation orientation = scrollbar.orientation();
615     GRefPtr<GtkStyleContext> styleContext = createChildStyleContext(parentContext, "button");
616
617     unsigned flags = 0;
618     if ((BackButtonStartPart == part && scrollbar.currentPos())
619         || (BackButtonEndPart == part && scrollbar.currentPos())
620         || (ForwardButtonEndPart == part && scrollbar.currentPos() != scrollbar.maximum())
621         || (ForwardButtonStartPart == part && scrollbar.currentPos() != scrollbar.maximum())) {
622         if (part == scrollbar.pressedPart())
623             flags |= GTK_STATE_FLAG_ACTIVE;
624         if (part == scrollbar.hoveredPart())
625             flags |= GTK_STATE_FLAG_PRELIGHT;
626     } else
627         flags |= GTK_STATE_FLAG_INSENSITIVE;
628     gtk_style_context_set_state(styleContext.get(), static_cast<GtkStateFlags>(flags));
629
630     gtk_render_background(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
631     gtk_render_frame(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
632
633     gfloat arrowScaling;
634     gtk_style_context_get_style(styleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
635
636     double arrowSize = std::min(rect.width(), rect.height()) * arrowScaling;
637     FloatPoint arrowPoint(rect.x() + (rect.width() - arrowSize) / 2, rect.y() + (rect.height() - arrowSize) / 2);
638
639     if (flags & GTK_STATE_FLAG_ACTIVE) {
640         gint arrowDisplacementX, arrowDisplacementY;
641         gtk_style_context_get_style(styleContext.get(), "arrow-displacement-x", &arrowDisplacementX, "arrow-displacement-y", &arrowDisplacementY, nullptr);
642         arrowPoint.move(arrowDisplacementX, arrowDisplacementY);
643     }
644
645     gdouble angle;
646     if (orientation == VerticalScrollbar)
647         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI : 0;
648     else
649         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI / 2 : 3 * (G_PI / 2);
650
651     gtk_render_arrow(styleContext.get(), context.platformContext()->cr(), angle, arrowPoint.x(), arrowPoint.y(), arrowSize);
652 }
653
654 static void adjustRectAccordingToMargin(GtkStyleContext* context, IntRect& rect)
655 {
656     GtkBorder margin;
657     gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
658     rect.move(margin.left, margin.top);
659     rect.contract(margin.left + margin.right, margin.top + margin.bottom);
660 }
661
662 bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
663 {
664     if (graphicsContext.paintingDisabled())
665         return false;
666
667     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
668
669     // Create the ScrollbarControlPartMask based on the damageRect
670     ScrollbarControlPartMask scrollMask = NoPart;
671
672     IntRect backButtonStartPaintRect;
673     IntRect backButtonEndPaintRect;
674     IntRect forwardButtonStartPaintRect;
675     IntRect forwardButtonEndPaintRect;
676     if (hasButtons(scrollbar)) {
677         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
678         if (damageRect.intersects(backButtonStartPaintRect))
679             scrollMask |= BackButtonStartPart;
680         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
681         if (damageRect.intersects(backButtonEndPaintRect))
682             scrollMask |= BackButtonEndPart;
683         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
684         if (damageRect.intersects(forwardButtonStartPaintRect))
685             scrollMask |= ForwardButtonStartPart;
686         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
687         if (damageRect.intersects(forwardButtonEndPaintRect))
688             scrollMask |= ForwardButtonEndPart;
689     }
690
691     IntRect trackPaintRect = trackRect(scrollbar, true);
692     if (damageRect.intersects(trackPaintRect))
693         scrollMask |= TrackBGPart;
694
695     gboolean troughUnderSteppers;
696     gtk_style_context_get_style(styleContext.get(), "trough-under-steppers", &troughUnderSteppers, nullptr);
697     if (troughUnderSteppers && (scrollMask & BackButtonStartPart
698             || scrollMask & BackButtonEndPart
699             || scrollMask & ForwardButtonStartPart
700             || scrollMask & ForwardButtonEndPart))
701         scrollMask |= TrackBGPart;
702
703     IntRect currentThumbRect;
704     if (hasThumb(scrollbar)) {
705         IntRect track = trackRect(scrollbar, false);
706         IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, track);
707         int thumbFat;
708         gtk_style_context_get_style(styleContext.get(), "slider-width", &thumbFat, nullptr);
709         if (scrollbar.orientation() == HorizontalScrollbar)
710             currentThumbRect = IntRect(trackRect.x() + thumbPosition(scrollbar), trackRect.y() + (trackRect.height() - thumbFat) / 2, thumbLength(scrollbar), thumbFat);
711         else
712             currentThumbRect = IntRect(trackRect.x() + (trackRect.width() - thumbFat) / 2, trackRect.y() + thumbPosition(scrollbar), thumbFat, thumbLength(scrollbar));
713         if (damageRect.intersects(currentThumbRect))
714             scrollMask |= ThumbPart;
715     }
716
717     if (scrollMask == NoPart)
718         return true;
719
720     ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart | ForwardButtonStartPart | ForwardButtonEndPart;
721
722     // Paint the track background. If the trough-under-steppers property is true, this
723     // should be the full size of the scrollbar, but if is false, it should only be the
724     // track rect.
725     GRefPtr<GtkStyleContext> troughStyleContext = createChildStyleContext(styleContext.get(), "trough");
726     if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons) {
727         IntRect fullScrollbarRect = trackPaintRect;
728         if (troughUnderSteppers)
729             fullScrollbarRect = scrollbar.frameRect();
730
731         IntRect adjustedRect = fullScrollbarRect;
732         adjustRectAccordingToMargin(styleContext.get(), adjustedRect);
733         gtk_render_background(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
734         gtk_render_frame(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
735
736         adjustedRect = fullScrollbarRect;
737         adjustRectAccordingToMargin(troughStyleContext.get(), adjustedRect);
738         gtk_render_background(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
739         gtk_render_frame(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
740     }
741
742     // Paint the back and forward buttons.
743     if (scrollMask & BackButtonStartPart)
744         paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
745     if (scrollMask & BackButtonEndPart)
746         paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
747     if (scrollMask & ForwardButtonStartPart)
748         paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
749     if (scrollMask & ForwardButtonEndPart)
750         paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
751
752     // Paint the thumb.
753     if (scrollMask & ThumbPart) {
754         GRefPtr<GtkStyleContext> thumbStyleContext = createChildStyleContext(troughStyleContext.get(), "slider");
755         unsigned flags = 0;
756         if (scrollbar.pressedPart() == ThumbPart)
757             flags |= GTK_STATE_FLAG_ACTIVE;
758         if (scrollbar.hoveredPart() == ThumbPart)
759             flags |= GTK_STATE_FLAG_PRELIGHT;
760         gtk_style_context_set_state(thumbStyleContext.get(), static_cast<GtkStateFlags>(flags));
761
762         IntRect thumbRect(currentThumbRect);
763         adjustRectAccordingToMargin(thumbStyleContext.get(), thumbRect);
764         gtk_render_slider(thumbStyleContext.get(), graphicsContext.platformContext()->cr(), thumbRect.x(), thumbRect.y(), thumbRect.width(), thumbRect.height(),
765             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
766     }
767
768     return true;
769 }
770 #endif // GTK_CHECK_VERSION(3, 20, 0)
771
772 ScrollbarButtonPressAction ScrollbarThemeGtk::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
773 {
774     gboolean warpSlider = FALSE;
775     switch (pressedPart) {
776     case BackTrackPart:
777     case ForwardTrackPart:
778         g_object_get(gtk_settings_get_default(),
779             "gtk-primary-button-warps-slider",
780             &warpSlider, nullptr);
781         // The shift key or middle/right button reverses the sense.
782         if (event.shiftKey() || event.button() != LeftButton)
783             warpSlider = !warpSlider;
784         return warpSlider ?
785             ScrollbarButtonPressAction::CenterOnThumb:
786             ScrollbarButtonPressAction::Scroll;
787     case ThumbPart:
788         if (event.button() != RightButton)
789             return ScrollbarButtonPressAction::StartDrag;
790         break;
791     case BackButtonStartPart:
792     case ForwardButtonStartPart:
793     case BackButtonEndPart:
794     case ForwardButtonEndPart:
795         return ScrollbarButtonPressAction::Scroll;
796     default:
797         break;
798     }
799
800     return ScrollbarButtonPressAction::None;
801 }
802
803 #if GTK_CHECK_VERSION(3, 20, 0)
804 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
805 {
806     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
807     scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
808     IntSize contentsPreferredSize = scrollbarWidget.contents().preferredSize();
809     contentsPreferredSize = contentsPreferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
810     IntSize preferredSize = contentsPreferredSize + scrollbarWidget.scrollbar().preferredSize() - scrollbarWidget.scrollbar().minimumSize();
811     return preferredSize.width();
812 }
813 #else
814 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
815 {
816     int thumbFat, troughBorderWidth;
817     gtk_style_context_get_style(createStyleContext().get(), "slider-width", &thumbFat, "trough-border", &troughBorderWidth, nullptr);
818     return thumbFat + 2 * troughBorderWidth;
819 }
820 #endif // GTK_CHECK_VERSION(3, 20, 0)
821
822 #if GTK_CHECK_VERSION(3, 20, 0)
823 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
824 {
825     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
826     scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
827     IntSize minSize = scrollbarWidget.slider().minimumSize();
828     return scrollbar.orientation() == VerticalScrollbar ? minSize.height() : minSize.width();
829 }
830 #else
831 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
832 {
833     int minThumbLength = 0;
834     gtk_style_context_get_style(createStyleContext(&scrollbar).get(), "min-slider-length", &minThumbLength, nullptr);
835     return minThumbLength;
836 }
837 #endif
838 }