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 "PlatformMouseEvent.h" 30 #include "RenderThemeGtk.h" 31 #include "ScrollView.h" 32 #include "Scrollbar.h" 33 34 namespace WebCore { 35 36 static HashSet<Scrollbar*>* gScrollbars; 37 38 ScrollbarTheme* ScrollbarTheme::nativeTheme() 39 { 40 static ScrollbarThemeGtk theme; 41 return &theme; 42 } 43 44 ScrollbarThemeGtk::~ScrollbarThemeGtk() 45 { 46 } 47 48 void ScrollbarThemeGtk::registerScrollbar(Scrollbar* scrollbar) 49 { 50 if (!gScrollbars) 51 gScrollbars = new HashSet<Scrollbar*>; 52 gScrollbars->add(scrollbar); 53 } 54 55 void ScrollbarThemeGtk::unregisterScrollbar(Scrollbar* scrollbar) 56 { 57 gScrollbars->remove(scrollbar); 58 if (gScrollbars->isEmpty()) { 59 delete gScrollbars; 60 gScrollbars = 0; 61 } 62 } 63 64 void ScrollbarThemeGtk::updateScrollbarsFrameThickness() 65 { 66 if (!gScrollbars) 67 return; 68 69 // Update the thickness of every interior frame scrollbar widget. The 70 // platform-independent scrollbar them code isn't yet smart enough to get 71 // this information when it paints. 72 HashSet<Scrollbar*>::iterator end = gScrollbars->end(); 73 for (HashSet<Scrollbar*>::iterator it = gScrollbars->begin(); it != end; ++it) { 74 Scrollbar* scrollbar = (*it); 75 76 // Top-level scrollbar i.e. scrollbars who have a parent ScrollView 77 // with no parent are native, and thus do not need to be resized. 78 if (!scrollbar->parent() || !scrollbar->parent()->parent()) 79 return; 80 81 int thickness = scrollbarThickness(scrollbar->controlSize()); 82 if (scrollbar->orientation() == HorizontalScrollbar) 83 scrollbar->setFrameRect(IntRect(0, scrollbar->parent()->height() - thickness, scrollbar->width(), thickness)); 84 else 85 scrollbar->setFrameRect(IntRect(scrollbar->parent()->width() - thickness, 0, thickness, scrollbar->height())); 86 } 87 } 88 89 bool ScrollbarThemeGtk::hasThumb(Scrollbar* scrollbar) 90 { 91 // This method is just called as a paint-time optimization to see if 92 // painting the thumb can be skipped. We don't have to be exact here. 93 return thumbLength(scrollbar) > 0; 94 } 95 96 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool) 97 { 98 if (part == BackButtonEndPart && !m_hasBackButtonEndPart) 99 return IntRect(); 100 101 int x = scrollbar->x() + m_troughBorderWidth; 102 int y = scrollbar->y() + m_troughBorderWidth; 103 IntSize size = buttonSize(scrollbar); 104 if (part == BackButtonStartPart) 105 return IntRect(x, y, size.width(), size.height()); 106 107 // BackButtonEndPart (alternate button) 108 if (scrollbar->orientation() == HorizontalScrollbar) 109 return IntRect(scrollbar->x() + scrollbar->width() - m_troughBorderWidth - (2 * size.width()), y, size.width(), size.height()); 110 111 // VerticalScrollbar alternate button 112 return IntRect(x, scrollbar->y() + scrollbar->height() - m_troughBorderWidth - (2 * size.height()), size.width(), size.height()); 113 } 114 115 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool) 116 { 117 if (part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) 118 return IntRect(); 119 120 IntSize size = buttonSize(scrollbar); 121 if (scrollbar->orientation() == HorizontalScrollbar) { 122 int y = scrollbar->y() + m_troughBorderWidth; 123 if (part == ForwardButtonEndPart) 124 return IntRect(scrollbar->x() + scrollbar->width() - size.width() - m_troughBorderWidth, y, size.width(), size.height()); 125 126 // ForwardButtonStartPart (alternate button) 127 return IntRect(scrollbar->x() + m_troughBorderWidth + size.width(), y, size.width(), size.height()); 128 } 129 130 // VerticalScrollbar 131 int x = scrollbar->x() + m_troughBorderWidth; 132 if (part == ForwardButtonEndPart) 133 return IntRect(x, scrollbar->y() + scrollbar->height() - size.height() - m_troughBorderWidth, size.width(), size.height()); 134 135 // ForwardButtonStartPart (alternate button) 136 return IntRect(x, scrollbar->y() + m_troughBorderWidth + size.height(), size.width(), size.height()); 137 } 138 139 IntRect ScrollbarThemeGtk::trackRect(Scrollbar* scrollbar, bool) 140 { 141 // The padding along the thumb movement axis (from outside to in) 142 // is the size of trough border plus the size of the stepper (button) 143 // plus the size of stepper spacing (the space between the stepper and 144 // the place where the thumb stops). There is often no stepper spacing. 145 int movementAxisPadding = m_troughBorderWidth + m_stepperSize + m_stepperSpacing; 146 147 // The fatness of the scrollbar on the non-movement axis. 148 int thickness = scrollbarThickness(scrollbar->controlSize()); 149 150 int alternateButtonOffset = 0; 151 int alternateButtonWidth = 0; 152 if (m_hasForwardButtonStartPart) { 153 alternateButtonOffset += m_stepperSize; 154 alternateButtonWidth += m_stepperSize; 155 } 156 if (m_hasBackButtonEndPart) 157 alternateButtonWidth += m_stepperSize; 158 159 if (scrollbar->orientation() == HorizontalScrollbar) { 160 // Once the scrollbar becomes smaller than the natural size of the 161 // two buttons, the track disappears. 162 if (scrollbar->width() < 2 * thickness) 163 return IntRect(); 164 return IntRect(scrollbar->x() + movementAxisPadding + alternateButtonOffset, scrollbar->y(), 165 scrollbar->width() - (2 * movementAxisPadding) - alternateButtonWidth, thickness); 166 } 167 168 if (scrollbar->height() < 2 * thickness) 169 return IntRect(); 170 return IntRect(scrollbar->x(), scrollbar->y() + movementAxisPadding + alternateButtonOffset, 171 thickness, scrollbar->height() - (2 * movementAxisPadding) - alternateButtonWidth); 172 } 173 174 IntRect ScrollbarThemeGtk::thumbRect(Scrollbar* scrollbar, const IntRect& unconstrainedTrackRect) 175 { 176 IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect); 177 int thumbPos = thumbPosition(scrollbar); 178 if (scrollbar->orientation() == HorizontalScrollbar) 179 return IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - m_thumbFatness) / 2, thumbLength(scrollbar), m_thumbFatness); 180 181 // VerticalScrollbar 182 return IntRect(trackRect.x() + (trackRect.width() - m_thumbFatness) / 2, trackRect.y() + thumbPos, m_thumbFatness, thumbLength(scrollbar)); 183 } 184 185 bool ScrollbarThemeGtk::paint(Scrollbar* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect) 186 { 187 if (graphicsContext->paintingDisabled()) 188 return false; 189 190 // Create the ScrollbarControlPartMask based on the damageRect 191 ScrollbarControlPartMask scrollMask = NoPart; 192 193 IntRect backButtonStartPaintRect; 194 IntRect backButtonEndPaintRect; 195 IntRect forwardButtonStartPaintRect; 196 IntRect forwardButtonEndPaintRect; 197 if (hasButtons(scrollbar)) { 198 backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true); 199 if (damageRect.intersects(backButtonStartPaintRect)) 200 scrollMask |= BackButtonStartPart; 201 backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true); 202 if (damageRect.intersects(backButtonEndPaintRect)) 203 scrollMask |= BackButtonEndPart; 204 forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true); 205 if (damageRect.intersects(forwardButtonStartPaintRect)) 206 scrollMask |= ForwardButtonStartPart; 207 forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true); 208 if (damageRect.intersects(forwardButtonEndPaintRect)) 209 scrollMask |= ForwardButtonEndPart; 210 } 211 212 IntRect trackPaintRect = trackRect(scrollbar, true); 213 if (damageRect.intersects(trackPaintRect)) 214 scrollMask |= TrackBGPart; 215 216 if (m_troughUnderSteppers && (scrollMask & BackButtonStartPart 217 || scrollMask & BackButtonEndPart 218 || scrollMask & ForwardButtonStartPart 219 || scrollMask & ForwardButtonEndPart)) 220 scrollMask |= TrackBGPart; 221 222 bool thumbPresent = hasThumb(scrollbar); 223 IntRect currentThumbRect; 224 if (thumbPresent) { 225 IntRect track = trackRect(scrollbar, false); 226 currentThumbRect = thumbRect(scrollbar, track); 227 if (damageRect.intersects(currentThumbRect)) 228 scrollMask |= ThumbPart; 229 } 230 231 ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart 232 | ForwardButtonStartPart | ForwardButtonEndPart; 233 if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons) 234 paintScrollbarBackground(graphicsContext, scrollbar); 235 paintTrackBackground(graphicsContext, scrollbar, trackPaintRect); 236 237 // Paint the back and forward buttons. 238 if (scrollMask & BackButtonStartPart) 239 paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart); 240 if (scrollMask & BackButtonEndPart) 241 paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart); 242 if (scrollMask & ForwardButtonStartPart) 243 paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart); 244 if (scrollMask & ForwardButtonEndPart) 245 paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart); 246 247 // Paint the thumb. 248 if (scrollMask & ThumbPart) 249 paintThumb(graphicsContext, scrollbar, currentThumbRect); 250 251 return true; 252 } 253 254 void ScrollbarThemeGtk::paintScrollCorner(ScrollView* view, GraphicsContext* context, const IntRect& cornerRect) 255 { 256 // ScrollbarThemeComposite::paintScrollCorner incorrectly assumes that the 257 // ScrollView is a FrameView (see FramelessScrollView), so we cannot let 258 // that code run. For FrameView's this is correct since we don't do custom 259 // scrollbar corner rendering, which ScrollbarThemeComposite supports. 260 ScrollbarTheme::paintScrollCorner(view, context, cornerRect); 261 } 262 263 bool ScrollbarThemeGtk::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& event) 264 { 265 return (event.shiftKey() && event.button() == LeftButton) || (event.button() == MiddleButton); 266 } 267 268 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize) 269 { 270 return m_thumbFatness + (m_troughBorderWidth * 2); 271 } 272 273 IntSize ScrollbarThemeGtk::buttonSize(Scrollbar* scrollbar) 274 { 275 if (scrollbar->orientation() == VerticalScrollbar) 276 return IntSize(m_thumbFatness, m_stepperSize); 277 278 // HorizontalScrollbar 279 return IntSize(m_stepperSize, m_thumbFatness); 280 } 281 282 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar* scrollbar) 283 { 284 return m_minThumbLength; 285 } 286 287 } 288 289