/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "ScrollbarThemeComposite.h" #include "Chrome.h" #include "ChromeClient.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsContext.h" #include "Page.h" #include "PlatformMouseEvent.h" #include "Scrollbar.h" #include "ScrollableArea.h" #include "Settings.h" using namespace std; namespace WebCore { #if PLATFORM(WIN) static Page* pageForScrollView(ScrollView* view) { if (!view) return 0; if (!view->isFrameView()) return 0; FrameView* frameView = static_cast(view); if (!frameView->frame()) return 0; return frameView->frame()->page(); } #endif bool ScrollbarThemeComposite::paint(Scrollbar* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect) { // Create the ScrollbarControlPartMask based on the damageRect ScrollbarControlPartMask scrollMask = NoPart; IntRect backButtonStartPaintRect; IntRect backButtonEndPaintRect; IntRect forwardButtonStartPaintRect; IntRect forwardButtonEndPaintRect; if (hasButtons(scrollbar)) { backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true); if (damageRect.intersects(backButtonStartPaintRect)) scrollMask |= BackButtonStartPart; backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true); if (damageRect.intersects(backButtonEndPaintRect)) scrollMask |= BackButtonEndPart; forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true); if (damageRect.intersects(forwardButtonStartPaintRect)) scrollMask |= ForwardButtonStartPart; forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true); if (damageRect.intersects(forwardButtonEndPaintRect)) scrollMask |= ForwardButtonEndPart; } IntRect startTrackRect; IntRect thumbRect; IntRect endTrackRect; IntRect trackPaintRect = trackRect(scrollbar, true); if (damageRect.intersects(trackPaintRect)) scrollMask |= TrackBGPart; bool thumbPresent = hasThumb(scrollbar); if (thumbPresent) { IntRect track = trackRect(scrollbar); splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect); if (damageRect.intersects(thumbRect)) scrollMask |= ThumbPart; if (damageRect.intersects(startTrackRect)) scrollMask |= BackTrackPart; if (damageRect.intersects(endTrackRect)) scrollMask |= ForwardTrackPart; } #if PLATFORM(WIN) // FIXME: This API makes the assumption that the custom scrollbar's metrics will match // the theme's metrics. This is not a valid assumption. The ability for a client to paint // custom scrollbars should be removed once scrollbars can be styled via CSS. if (Page* page = pageForScrollView(scrollbar->parent())) { if (page->settings()->shouldPaintCustomScrollbars()) { float proportion = static_cast(scrollbar->visibleSize()) / scrollbar->totalSize(); float value = scrollbar->currentPos() / static_cast(scrollbar->maximum()); ScrollbarControlState s = 0; if (scrollbar->scrollableArea()->isActive()) s |= ActiveScrollbarState; if (scrollbar->enabled()) s |= EnabledScrollbarState; if (scrollbar->pressedPart() != NoPart) s |= PressedScrollbarState; if (page->chrome()->client()->paintCustomScrollbar(graphicsContext, scrollbar->frameRect(), scrollbar->controlSize(), s, scrollbar->pressedPart(), scrollbar->orientation() == VerticalScrollbar, value, proportion, scrollMask)) return true; } } #endif // Paint the scrollbar background (only used by custom CSS scrollbars). paintScrollbarBackground(graphicsContext, scrollbar); // Paint the back and forward buttons. if (scrollMask & BackButtonStartPart) paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart); if (scrollMask & BackButtonEndPart) paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart); if (scrollMask & ForwardButtonStartPart) paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart); if (scrollMask & ForwardButtonEndPart) paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart); if (scrollMask & TrackBGPart) paintTrackBackground(graphicsContext, scrollbar, trackPaintRect); if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) { // Paint the track pieces above and below the thumb. if (scrollMask & BackTrackPart) paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart); if (scrollMask & ForwardTrackPart) paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart); paintTickmarks(graphicsContext, scrollbar, trackPaintRect); } // Paint the thumb. if (scrollMask & ThumbPart) paintThumb(graphicsContext, scrollbar, thumbRect); return true; } ScrollbarPart ScrollbarThemeComposite::hitTest(Scrollbar* scrollbar, const PlatformMouseEvent& evt) { ScrollbarPart result = NoPart; if (!scrollbar->enabled()) return result; IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.pos()); mousePosition.move(scrollbar->x(), scrollbar->y()); if (!scrollbar->frameRect().contains(mousePosition)) return NoPart; result = ScrollbarBGPart; IntRect track = trackRect(scrollbar); if (track.contains(mousePosition)) { IntRect beforeThumbRect; IntRect thumbRect; IntRect afterThumbRect; splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect); if (thumbRect.contains(mousePosition)) result = ThumbPart; else if (beforeThumbRect.contains(mousePosition)) result = BackTrackPart; else if (afterThumbRect.contains(mousePosition)) result = ForwardTrackPart; else result = TrackBGPart; } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(mousePosition)) result = BackButtonStartPart; else if (backButtonRect(scrollbar, BackButtonEndPart).contains(mousePosition)) result = BackButtonEndPart; else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(mousePosition)) result = ForwardButtonStartPart; else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(mousePosition)) result = ForwardButtonEndPart; return result; } void ScrollbarThemeComposite::invalidatePart(Scrollbar* scrollbar, ScrollbarPart part) { if (part == NoPart) return; IntRect result; switch (part) { case BackButtonStartPart: result = backButtonRect(scrollbar, BackButtonStartPart, true); break; case BackButtonEndPart: result = backButtonRect(scrollbar, BackButtonEndPart, true); break; case ForwardButtonStartPart: result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true); break; case ForwardButtonEndPart: result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true); break; case TrackBGPart: result = trackRect(scrollbar, true); break; case ScrollbarBGPart: result = scrollbar->frameRect(); break; default: { IntRect beforeThumbRect, thumbRect, afterThumbRect; splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect); if (part == BackTrackPart) result = beforeThumbRect; else if (part == ForwardTrackPart) result = afterThumbRect; else result = thumbRect; } } result.move(-scrollbar->x(), -scrollbar->y()); scrollbar->invalidateRect(result); } void ScrollbarThemeComposite::splitTrack(Scrollbar* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect) { // This function won't even get called unless we're big enough to have some combination of these three rects where at least // one of them is non-empty. IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect); int thickness = scrollbar->orientation() == HorizontalScrollbar ? scrollbar->height() : scrollbar->width(); int thumbPos = thumbPosition(scrollbar); if (scrollbar->orientation() == HorizontalScrollbar) { thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - thickness) / 2, thumbLength(scrollbar), thickness); beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height()); afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height()); } else { thumbRect = IntRect(trackRect.x() + (trackRect.width() - thickness) / 2, trackRect.y() + thumbPos, thickness, thumbLength(scrollbar)); beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2); afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY()); } } // Returns the size represented by track taking into account scrolling past // the end of the document. static float usedTotalSize(Scrollbar* scrollbar) { float overhangAtStart = -scrollbar->currentPos(); float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize(); float overhang = max(0.0f, max(overhangAtStart, overhangAtEnd)); return scrollbar->totalSize() + overhang; } int ScrollbarThemeComposite::thumbPosition(Scrollbar* scrollbar) { if (scrollbar->enabled()) return max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / (usedTotalSize(scrollbar) - scrollbar->visibleSize()); return 0; } int ScrollbarThemeComposite::thumbLength(Scrollbar* scrollbar) { if (!scrollbar->enabled()) return 0; float proportion = scrollbar->visibleSize() / usedTotalSize(scrollbar); int trackLen = trackLength(scrollbar); int length = proportion * trackLen; length = max(length, minimumThumbLength(scrollbar)); if (length > trackLen) length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track). return length; } int ScrollbarThemeComposite::minimumThumbLength(Scrollbar* scrollbar) { return scrollbarThickness(scrollbar->controlSize()); } int ScrollbarThemeComposite::trackPosition(Scrollbar* scrollbar) { IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar)); return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y(); } int ScrollbarThemeComposite::trackLength(Scrollbar* scrollbar) { IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar)); return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height(); } void ScrollbarThemeComposite::paintScrollCorner(ScrollView* view, GraphicsContext* context, const IntRect& cornerRect) { FrameView* frameView = static_cast(view); Page* page = frameView->frame() ? frameView->frame()->page() : 0; if (page && page->settings()->shouldPaintCustomScrollbars() && page->chrome()->client()->paintCustomScrollCorner(context, cornerRect)) return; context->fillRect(cornerRect, Color::white, ColorSpaceDeviceRGB); } }