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