Unreviewed, rolling out r194362.
[WebKit-https.git] / Source / WebCore / platform / gtk / ScrollbarThemeGtk.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
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 "ScrollbarThemeGtk.h"
28
29 #include "PlatformContextCairo.h"
30 #include "PlatformMouseEvent.h"
31 #include "ScrollView.h"
32 #include "Scrollbar.h"
33 #include <gtk/gtk.h>
34 #include <wtf/NeverDestroyed.h>
35 #include <wtf/glib/GRefPtr.h>
36
37 namespace WebCore {
38
39 ScrollbarTheme& ScrollbarTheme::nativeTheme()
40 {
41     static ScrollbarThemeGtk theme;
42     return theme;
43 }
44
45 ScrollbarThemeGtk::~ScrollbarThemeGtk()
46 {
47 }
48
49 bool ScrollbarThemeGtk::hasThumb(Scrollbar& scrollbar)
50 {
51 #ifndef GTK_API_VERSION_2
52     // This method is just called as a paint-time optimization to see if
53     // painting the thumb can be skipped.  We don't have to be exact here.
54     return thumbLength(scrollbar) > 0;
55 #else
56     UNUSED_PARAM(scrollbar);
57     return false;
58 #endif
59 }
60
61 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool)
62 {
63 #ifndef GTK_API_VERSION_2
64     if (part == BackButtonEndPart && !m_hasBackButtonEndPart)
65         return IntRect();
66     if (part == BackButtonStartPart && !m_hasBackButtonStartPart)
67         return IntRect();
68
69     int x = scrollbar.x() + m_troughBorderWidth;
70     int y = scrollbar.y() + m_troughBorderWidth;
71     IntSize size = buttonSize(scrollbar);
72     if (part == BackButtonStartPart)
73         return IntRect(x, y, size.width(), size.height());
74
75     // BackButtonEndPart (alternate button)
76     if (scrollbar.orientation() == HorizontalScrollbar)
77         return IntRect(scrollbar.x() + scrollbar.width() - m_troughBorderWidth - (2 * size.width()), y, size.width(), size.height());
78
79     // VerticalScrollbar alternate button
80     return IntRect(x, scrollbar.y() + scrollbar.height() - m_troughBorderWidth - (2 * size.height()), size.width(), size.height());
81 #else
82     UNUSED_PARAM(scrollbar);
83     UNUSED_PARAM(part);
84     return IntRect();
85 #endif
86 }
87
88 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool)
89 {
90 #ifndef GTK_API_VERSION_2
91     if (part == ForwardButtonStartPart && !m_hasForwardButtonStartPart)
92         return IntRect();
93     if (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart)
94         return IntRect();
95
96     IntSize size = buttonSize(scrollbar);
97     if (scrollbar.orientation() == HorizontalScrollbar) {
98         int y = scrollbar.y() + m_troughBorderWidth;
99         if (part == ForwardButtonEndPart)
100             return IntRect(scrollbar.x() + scrollbar.width() - size.width() - m_troughBorderWidth, y, size.width(), size.height());
101
102         // ForwardButtonStartPart (alternate button)
103         return IntRect(scrollbar.x() + m_troughBorderWidth + size.width(), y, size.width(), size.height());
104     }
105
106     // VerticalScrollbar
107     int x = scrollbar.x() + m_troughBorderWidth;
108     if (part == ForwardButtonEndPart)
109         return IntRect(x, scrollbar.y() + scrollbar.height() - size.height() - m_troughBorderWidth, size.width(), size.height());
110
111     // ForwardButtonStartPart (alternate button)
112     return IntRect(x, scrollbar.y() + m_troughBorderWidth + size.height(), size.width(), size.height());
113 #else
114     UNUSED_PARAM(scrollbar);
115     UNUSED_PARAM(part);
116     return IntRect();
117 #endif
118 }
119
120 IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool)
121 {
122 #ifndef GTK_API_VERSION_2
123     // The padding along the thumb movement axis includes the trough border
124     // plus the size of stepper spacing (the space between the stepper and
125     // the place where the thumb stops). There is often no stepper spacing.
126     int movementAxisPadding = m_troughBorderWidth + m_stepperSpacing;
127
128     // The fatness of the scrollbar on the non-movement axis.
129     int thickness = scrollbarThickness(scrollbar.controlSize());
130
131     int startButtonsOffset = 0;
132     int buttonsWidth = 0;
133     if (m_hasForwardButtonStartPart) {
134         startButtonsOffset += m_stepperSize;
135         buttonsWidth += m_stepperSize;
136     }
137     if (m_hasBackButtonStartPart) {
138         startButtonsOffset += m_stepperSize;
139         buttonsWidth += m_stepperSize;
140     }
141     if (m_hasBackButtonEndPart)
142         buttonsWidth += m_stepperSize;
143     if (m_hasForwardButtonEndPart)
144         buttonsWidth += m_stepperSize;
145
146     if (scrollbar.orientation() == HorizontalScrollbar) {
147         // Once the scrollbar becomes smaller than the natural size of the
148         // two buttons, the track disappears.
149         if (scrollbar.width() < 2 * thickness)
150             return IntRect();
151         return IntRect(scrollbar.x() + movementAxisPadding + startButtonsOffset, scrollbar.y(),
152                        scrollbar.width() - (2 * movementAxisPadding) - buttonsWidth, thickness);
153     }
154
155     if (scrollbar.height() < 2 * thickness)
156         return IntRect();
157     return IntRect(scrollbar.x(), scrollbar.y() + movementAxisPadding + startButtonsOffset,
158                    thickness, scrollbar.height() - (2 * movementAxisPadding) - buttonsWidth);
159 #else
160     UNUSED_PARAM(scrollbar);
161     return IntRect();
162 #endif
163 }
164
165 #ifndef GTK_API_VERSION_2
166 class ScrollbarStyleContext {
167     WTF_MAKE_NONCOPYABLE(ScrollbarStyleContext); WTF_MAKE_FAST_ALLOCATED;
168 public:
169     ScrollbarStyleContext()
170         : m_context(adoptGRef(gtk_style_context_new()))
171     {
172         GtkWidgetPath* path = gtk_widget_path_new();
173         gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
174         gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_SCROLLBAR);
175         gtk_style_context_set_path(m_context.get(), path);
176         gtk_widget_path_free(path);
177     }
178
179     ~ScrollbarStyleContext()
180     {
181     }
182
183     GtkStyleContext* context() const { return m_context.get(); }
184
185 private:
186     GRefPtr<GtkStyleContext> m_context;
187 };
188
189 static GtkStyleContext* gtkScrollbarStyleContext()
190 {
191     static NeverDestroyed<ScrollbarStyleContext> styleContext;
192     return styleContext.get().context();
193 }
194
195 ScrollbarThemeGtk::ScrollbarThemeGtk()
196 {
197     updateThemeProperties();
198 }
199
200 void ScrollbarThemeGtk::themeChanged()
201 {
202     gtk_style_context_invalidate(gtkScrollbarStyleContext());
203     updateThemeProperties();
204 }
205
206 void ScrollbarThemeGtk::updateThemeProperties()
207 {
208     gtk_style_context_get_style(
209         gtkScrollbarStyleContext(),
210         "min-slider-length", &m_minThumbLength,
211         "slider-width", &m_thumbFatness,
212         "trough-border", &m_troughBorderWidth,
213         "stepper-size", &m_stepperSize,
214         "stepper-spacing", &m_stepperSpacing,
215         "trough-under-steppers", &m_troughUnderSteppers,
216         "has-backward-stepper", &m_hasBackButtonStartPart,
217         "has-forward-stepper", &m_hasForwardButtonEndPart,
218         "has-secondary-backward-stepper", &m_hasBackButtonEndPart,
219         "has-secondary-forward-stepper", &m_hasForwardButtonStartPart,
220         nullptr);
221     updateScrollbarsFrameThickness();
222 }
223
224 typedef HashSet<Scrollbar*> ScrollbarMap;
225
226 static ScrollbarMap& scrollbarMap()
227 {
228     static NeverDestroyed<ScrollbarMap> map;
229     return map;
230 }
231
232 void ScrollbarThemeGtk::registerScrollbar(Scrollbar& scrollbar)
233 {
234     scrollbarMap().add(&scrollbar);
235 }
236
237 void ScrollbarThemeGtk::unregisterScrollbar(Scrollbar& scrollbar)
238 {
239     scrollbarMap().remove(&scrollbar);
240 }
241
242 void ScrollbarThemeGtk::updateScrollbarsFrameThickness()
243 {
244     if (scrollbarMap().isEmpty())
245         return;
246
247     // Update the thickness of every interior frame scrollbar widget. The
248     // platform-independent scrollbar them code isn't yet smart enough to get
249     // this information when it paints.
250     for (const auto& scrollbar : scrollbarMap()) {
251         // Top-level scrollbar i.e. scrollbars who have a parent ScrollView
252         // with no parent are native, and thus do not need to be resized.
253         if (!scrollbar->parent() || !scrollbar->parent()->parent())
254             return;
255
256         int thickness = scrollbarThickness(scrollbar->controlSize());
257         if (scrollbar->orientation() == HorizontalScrollbar)
258             scrollbar->setFrameRect(IntRect(0, scrollbar->parent()->height() - thickness, scrollbar->width(), thickness));
259         else
260             scrollbar->setFrameRect(IntRect(scrollbar->parent()->width() - thickness, 0, thickness, scrollbar->height()));
261     }
262 }
263
264 IntRect ScrollbarThemeGtk::thumbRect(Scrollbar& scrollbar, const IntRect& unconstrainedTrackRect)
265 {
266     IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
267     int thumbPos = thumbPosition(scrollbar);
268     if (scrollbar.orientation() == HorizontalScrollbar)
269         return IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - m_thumbFatness) / 2, thumbLength(scrollbar), m_thumbFatness); 
270
271     // VerticalScrollbar
272     return IntRect(trackRect.x() + (trackRect.width() - m_thumbFatness) / 2, trackRect.y() + thumbPos, m_thumbFatness, thumbLength(scrollbar));
273 }
274
275 static void applyScrollbarStyleContextClasses(GtkStyleContext* context, ScrollbarOrientation orientation)
276 {
277     gtk_style_context_add_class(context, GTK_STYLE_CLASS_SCROLLBAR);
278     gtk_style_context_add_class(context, orientation == VerticalScrollbar ?  GTK_STYLE_CLASS_VERTICAL : GTK_STYLE_CLASS_HORIZONTAL);
279 }
280
281 static void adjustRectAccordingToMargin(GtkStyleContext* context, GtkStateFlags state, IntRect& rect)
282 {
283     GtkBorder margin;
284     gtk_style_context_set_state(context, state);
285     gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
286     rect.move(margin.left, margin.right);
287     rect.contract(margin.left + margin.right, margin.top + margin.bottom);
288 }
289
290 void ScrollbarThemeGtk::paintTrackBackground(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect)
291 {
292     // Paint the track background. If the trough-under-steppers property is true, this
293     // should be the full size of the scrollbar, but if is false, it should only be the
294     // track rect.
295     IntRect fullScrollbarRect(rect);
296     if (m_troughUnderSteppers)
297         fullScrollbarRect = IntRect(scrollbar.x(), scrollbar.y(), scrollbar.width(), scrollbar.height());
298
299     GtkStyleContext* styleContext = gtkScrollbarStyleContext();
300     gtk_style_context_save(styleContext);
301
302     applyScrollbarStyleContextClasses(styleContext, scrollbar.orientation());
303     gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_TROUGH);
304
305     adjustRectAccordingToMargin(styleContext, static_cast<GtkStateFlags>(0), fullScrollbarRect);
306     gtk_render_background(styleContext, context.platformContext()->cr(), fullScrollbarRect.x(), fullScrollbarRect.y(), fullScrollbarRect.width(), fullScrollbarRect.height());
307     gtk_render_frame(styleContext, context.platformContext()->cr(), fullScrollbarRect.x(), fullScrollbarRect.y(), fullScrollbarRect.width(), fullScrollbarRect.height());
308
309     gtk_style_context_restore(styleContext);
310 }
311
312 void ScrollbarThemeGtk::paintScrollbarBackground(GraphicsContext& context, Scrollbar& scrollbar)
313 {
314     GtkStyleContext* styleContext = gtkScrollbarStyleContext();
315     gtk_style_context_save(styleContext);
316
317     applyScrollbarStyleContextClasses(styleContext, scrollbar.orientation());
318     gtk_style_context_add_class(styleContext, "scrolled-window");
319     gtk_render_frame(styleContext, context.platformContext()->cr(), scrollbar.x(), scrollbar.y(), scrollbar.width(), scrollbar.height());
320
321     gtk_style_context_restore(styleContext);
322 }
323
324 void ScrollbarThemeGtk::paintThumb(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect)
325 {
326     GtkStyleContext* styleContext = gtkScrollbarStyleContext();
327     gtk_style_context_save(styleContext);
328
329     ScrollbarOrientation orientation = scrollbar.orientation();
330     applyScrollbarStyleContextClasses(styleContext, orientation);
331     gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_SLIDER);
332
333     guint flags = 0;
334     if (scrollbar.pressedPart() == ThumbPart)
335         flags |= GTK_STATE_FLAG_ACTIVE;
336     if (scrollbar.hoveredPart() == ThumbPart)
337         flags |= GTK_STATE_FLAG_PRELIGHT;
338     gtk_style_context_set_state(styleContext, static_cast<GtkStateFlags>(flags));
339
340     IntRect thumbRect(rect);
341     adjustRectAccordingToMargin(styleContext, static_cast<GtkStateFlags>(flags), thumbRect);
342     gtk_render_slider(styleContext, context.platformContext()->cr(), thumbRect.x(), thumbRect.y(), thumbRect.width(), thumbRect.height(),
343         orientation == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
344
345     gtk_style_context_restore(styleContext);
346 }
347
348 void ScrollbarThemeGtk::paintButton(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part)
349 {
350     GtkStyleContext* styleContext = gtkScrollbarStyleContext();
351     gtk_style_context_save(styleContext);
352
353     ScrollbarOrientation orientation = scrollbar.orientation();
354     applyScrollbarStyleContextClasses(styleContext, orientation);
355
356     guint flags = 0;
357     if ((BackButtonStartPart == part && scrollbar.currentPos())
358         || (BackButtonEndPart == part && scrollbar.currentPos())
359         || (ForwardButtonEndPart == part && scrollbar.currentPos() != scrollbar.maximum())
360         || (ForwardButtonStartPart == part && scrollbar.currentPos() != scrollbar.maximum())) {
361         if (part == scrollbar.pressedPart())
362             flags |= GTK_STATE_FLAG_ACTIVE;
363         if (part == scrollbar.hoveredPart())
364             flags |= GTK_STATE_FLAG_PRELIGHT;
365     } else
366         flags |= GTK_STATE_FLAG_INSENSITIVE;
367     gtk_style_context_set_state(styleContext, static_cast<GtkStateFlags>(flags));
368
369     gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_BUTTON);
370     gtk_render_background(styleContext, context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
371     gtk_render_frame(styleContext, context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
372
373     gfloat arrowScaling;
374     gtk_style_context_get_style(styleContext, "arrow-scaling", &arrowScaling, nullptr);
375
376     double arrowSize = std::min(rect.width(), rect.height()) * arrowScaling;
377     FloatPoint arrowPoint(
378         rect.x() + (rect.width() - arrowSize) / 2,
379         rect.y() + (rect.height() - arrowSize) / 2);
380
381     if (flags & GTK_STATE_FLAG_ACTIVE) {
382         gint arrowDisplacementX, arrowDisplacementY;
383         gtk_style_context_get_style(styleContext, "arrow-displacement-x", &arrowDisplacementX, "arrow-displacement-y", &arrowDisplacementY, nullptr);
384         arrowPoint.move(arrowDisplacementX, arrowDisplacementY);
385     }
386
387     gdouble angle;
388     if (orientation == VerticalScrollbar)
389         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI : 0;
390     else
391         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI / 2 : 3 * (G_PI / 2);
392
393     gtk_render_arrow(styleContext, context.platformContext()->cr(), angle, arrowPoint.x(), arrowPoint.y(), arrowSize);
394
395     gtk_style_context_restore(styleContext);
396 }
397
398 bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
399 {
400     if (graphicsContext.paintingDisabled())
401         return false;
402
403     // Create the ScrollbarControlPartMask based on the damageRect
404     ScrollbarControlPartMask scrollMask = NoPart;
405
406     IntRect backButtonStartPaintRect;
407     IntRect backButtonEndPaintRect;
408     IntRect forwardButtonStartPaintRect;
409     IntRect forwardButtonEndPaintRect;
410     if (hasButtons(scrollbar)) {
411         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
412         if (damageRect.intersects(backButtonStartPaintRect))
413             scrollMask |= BackButtonStartPart;
414         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
415         if (damageRect.intersects(backButtonEndPaintRect))
416             scrollMask |= BackButtonEndPart;
417         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
418         if (damageRect.intersects(forwardButtonStartPaintRect))
419             scrollMask |= ForwardButtonStartPart;
420         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
421         if (damageRect.intersects(forwardButtonEndPaintRect))
422             scrollMask |= ForwardButtonEndPart;
423     }
424
425     IntRect trackPaintRect = trackRect(scrollbar, true);
426     if (damageRect.intersects(trackPaintRect))
427         scrollMask |= TrackBGPart;
428
429     if (m_troughUnderSteppers && (scrollMask & BackButtonStartPart
430             || scrollMask & BackButtonEndPart
431             || scrollMask & ForwardButtonStartPart
432             || scrollMask & ForwardButtonEndPart))
433         scrollMask |= TrackBGPart;
434
435     bool thumbPresent = hasThumb(scrollbar);
436     IntRect currentThumbRect;
437     if (thumbPresent) {
438         IntRect track = trackRect(scrollbar, false);
439         currentThumbRect = thumbRect(scrollbar, track);
440         if (damageRect.intersects(currentThumbRect))
441             scrollMask |= ThumbPart;
442     }
443
444     ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart | ForwardButtonStartPart | ForwardButtonEndPart;
445     if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons)
446         paintScrollbarBackground(graphicsContext, scrollbar);
447         paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
448
449     // Paint the back and forward buttons.
450     if (scrollMask & BackButtonStartPart)
451         paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
452     if (scrollMask & BackButtonEndPart)
453         paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
454     if (scrollMask & ForwardButtonStartPart)
455         paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
456     if (scrollMask & ForwardButtonEndPart)
457         paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
458
459     // Paint the thumb.
460     if (scrollMask & ThumbPart)
461         paintThumb(graphicsContext, scrollbar, currentThumbRect);
462
463     return true;
464 }
465
466 bool ScrollbarThemeGtk::shouldCenterOnThumb(Scrollbar&, const PlatformMouseEvent& event)
467 {
468     return (event.shiftKey() && event.button() == LeftButton) || (event.button() == MiddleButton);
469 }
470
471 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize)
472 {
473     return m_thumbFatness + (m_troughBorderWidth * 2);
474 }
475
476 IntSize ScrollbarThemeGtk::buttonSize(Scrollbar& scrollbar)
477 {
478     if (scrollbar.orientation() == VerticalScrollbar)
479         return IntSize(m_thumbFatness, m_stepperSize);
480
481     // HorizontalScrollbar
482     return IntSize(m_stepperSize, m_thumbFatness);
483 }
484
485 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar&)
486 {
487     return m_minThumbLength;
488 }
489 #endif // GTK_API_VERSION_2
490
491 }