diff options
Diffstat (limited to 'WebCore/rendering/RenderSlider.cpp')
-rw-r--r-- | WebCore/rendering/RenderSlider.cpp | 397 |
1 files changed, 221 insertions, 176 deletions
diff --git a/WebCore/rendering/RenderSlider.cpp b/WebCore/rendering/RenderSlider.cpp index 25f3e40..08ebadf 100644 --- a/WebCore/rendering/RenderSlider.cpp +++ b/WebCore/rendering/RenderSlider.cpp @@ -1,6 +1,5 @@ -/** - * - * Copyright (C) 2006, 2007, 2008 Apple Computer, Inc. +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -33,7 +32,9 @@ #include "HTMLNames.h" #include "MediaControlElements.h" #include "MouseEvent.h" +#include "RenderLayer.h" #include "RenderTheme.h" +#include "RenderView.h" #include <wtf/MathExtras.h> #ifdef ANDROID_LAYOUT @@ -46,44 +47,115 @@ namespace WebCore { using namespace HTMLNames; -const int defaultTrackLength = 129; +static const int defaultTrackLength = 129; + +// FIXME: The SliderRange class and functions are entirely based on the DOM, +// and could be put with HTMLInputElement (possibly with a new name) instead of here. +struct SliderRange { + bool isIntegral; + double minimum; + double maximum; + + explicit SliderRange(HTMLInputElement*); + double clampValue(double value); + + // Map value into 0-1 range + double proportionFromValue(double value) + { + if (minimum == maximum) + return 0; + + return (value - minimum) / (maximum - minimum); + } + + // Map from 0-1 range to value + double valueFromProportion(double proportion) + { + return minimum + proportion * (maximum - minimum); + } + + double valueFromElement(HTMLInputElement*, bool* wasClamped = 0); +}; + +SliderRange::SliderRange(HTMLInputElement* element) +{ + // FIXME: What's the right way to handle an integral range with non-integral minimum and maximum? + // Currently values are guaranteed to be integral but could be outside the range in that case. + + isIntegral = !equalIgnoringCase(element->getAttribute(precisionAttr), "float"); -class HTMLSliderThumbElement : public HTMLDivElement { + // FIXME: This treats maximum strings that can't be parsed as 0, but perhaps 100 would be more appropriate. + const AtomicString& maxString = element->getAttribute(maxAttr); + maximum = maxString.isNull() ? 100.0 : maxString.toDouble(); + + // If the maximum is smaller, use it as the minimum. + minimum = min(element->getAttribute(minAttr).toDouble(), maximum); +} + +double SliderRange::clampValue(double value) +{ + double clampedValue = max(minimum, min(value, maximum)); + return isIntegral ? round(clampedValue) : clampedValue; +} + +double SliderRange::valueFromElement(HTMLInputElement* element, bool* wasClamped) +{ + String valueString = element->value(); + double oldValue = valueString.isNull() ? (minimum + maximum) / 2 : valueString.toDouble(); + double newValue = clampValue(oldValue); + + if (wasClamped) + *wasClamped = valueString.isNull() || newValue != oldValue; + + return newValue; +} + +// Returns a value between 0 and 1. +// As with SliderRange, this could be on HTMLInputElement instead of here. +static double sliderPosition(HTMLInputElement* element) +{ + SliderRange range(element); + return range.proportionFromValue(range.valueFromElement(element)); +} + +class SliderThumbElement : public HTMLDivElement { public: - HTMLSliderThumbElement(Document*, Node* shadowParent = 0); - + SliderThumbElement(Document*, Node* shadowParent); + + bool inDragMode() const { return m_inDragMode; } + virtual void defaultEventHandler(Event*); + +private: virtual bool isShadowNode() const { return true; } virtual Node* shadowParentNode() { return m_shadowParent; } - - bool inDragMode() const { return m_inDragMode; } -private: + Node* m_shadowParent; FloatPoint m_initialClickPoint; // initial click point in RenderSlider-local coordinates int m_initialPosition; bool m_inDragMode; }; -HTMLSliderThumbElement::HTMLSliderThumbElement(Document* doc, Node* shadowParent) - : HTMLDivElement(divTag, doc) +SliderThumbElement::SliderThumbElement(Document* document, Node* shadowParent) + : HTMLDivElement(divTag, document) , m_shadowParent(shadowParent) - , m_initialClickPoint(IntPoint()) , m_initialPosition(0) , m_inDragMode(false) { } -void HTMLSliderThumbElement::defaultEventHandler(Event* event) +void SliderThumbElement::defaultEventHandler(Event* event) { const AtomicString& eventType = event->type(); if (eventType == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); RenderSlider* slider; - if (document()->frame() && renderer() && renderer()->parent() && + if (document()->frame() && renderer() && (slider = static_cast<RenderSlider*>(renderer()->parent())) && slider->mouseEventIsInThumb(mouseEvent)) { + // Cache the initial point where the mouse down occurred, in slider coordinates - m_initialClickPoint = slider->absoluteToLocal(FloatPoint(mouseEvent->pageX(), mouseEvent->pageY()), false, true); + m_initialClickPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true); // Cache the initial position of the thumb. m_initialPosition = slider->currentPosition(); m_inDragMode = true; @@ -106,16 +178,11 @@ void HTMLSliderThumbElement::defaultEventHandler(Event* event) // Move the slider MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); RenderSlider* slider = static_cast<RenderSlider*>(renderer()->parent()); - FloatPoint curPoint = slider->absoluteToLocal(FloatPoint(mouseEvent->pageX(), mouseEvent->pageY()), false, true); - int newPosition = slider->positionForOffset( - IntPoint(m_initialPosition + curPoint.x() - m_initialClickPoint.x() - + (renderBox()->width() / 2), - m_initialPosition + curPoint.y() - m_initialClickPoint.y() - + (renderBox()->height() / 2))); - if (slider->currentPosition() != newPosition) { - slider->setCurrentPosition(newPosition); - slider->valueChanged(); - } + + FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true); + IntPoint eventOffset(m_initialPosition + curPoint.x() - m_initialClickPoint.x() + renderBox()->width() / 2, + m_initialPosition + curPoint.y() - m_initialClickPoint.y() + renderBox()->height() / 2); + slider->setValueForPosition(slider->positionForOffset(eventOffset)); event->setDefaultHandled(); return; } @@ -126,7 +193,6 @@ void HTMLSliderThumbElement::defaultEventHandler(Event* event) RenderSlider::RenderSlider(HTMLInputElement* element) : RenderBlock(element) - , m_thumb(0) { } @@ -171,20 +237,20 @@ void RenderSlider::calcPrefWidths() setPrefWidthsDirty(false); } -void RenderSlider::styleDidChange(RenderStyle::Diff diff, const RenderStyle* oldStyle) +void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderBlock::styleDidChange(diff, oldStyle); - + if (m_thumb) - m_thumb->renderer()->setStyle(createThumbStyle(style(), m_thumb->renderer()->style())); - + m_thumb->renderer()->setStyle(createThumbStyle(style())); + setReplaced(isInline()); } -PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle, const RenderStyle* oldStyle) +PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle) { RefPtr<RenderStyle> style; - RenderStyle* pseudoStyle = getCachedPseudoStyle(RenderStyle::SLIDER_THUMB); + RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB); if (pseudoStyle) // We may be sharing style with another slider, but we must not share the thumb style. style = RenderStyle::clone(pseudoStyle); @@ -195,16 +261,11 @@ PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parent style->inheritFrom(parentStyle); style->setDisplay(BLOCK); - style->setPosition(RelativePosition); - if (oldStyle) { - style->setLeft(oldStyle->left()); - style->setTop(oldStyle->top()); - } if (parentStyle->appearance() == SliderVerticalPart) - style->setAppearance(SliderThumbVerticalPart); + style->setAppearance(SliderThumbVerticalPart); else if (parentStyle->appearance() == SliderHorizontalPart) - style->setAppearance(SliderThumbHorizontalPart); + style->setAppearance(SliderThumbHorizontalPart); else if (parentStyle->appearance() == MediaSliderPart) style->setAppearance(MediaSliderThumbPart); @@ -212,54 +273,97 @@ PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parent } void RenderSlider::layout() -{ - bool relayoutChildren = false; - - if (m_thumb && m_thumb->renderer()) { - -#ifdef ANDROID_LAYOUT - int oldVisibleWidth = m_visibleWidth; -#endif - - int oldWidth = width(); - calcWidth(); - int oldHeight = height(); - calcHeight(); - - if (oldWidth != width() || oldHeight != height()) - relayoutChildren = true; +{ + ASSERT(needsLayout()); -#ifdef ANDROID_LAYOUT - const Settings* settings = document()->settings(); - ASSERT(settings); - if (oldVisibleWidth != m_visibleWidth - && settings->layoutAlgorithm() == Settings::kLayoutFitColumnToScreen) - relayoutChildren = true; -#endif - - // Allow the theme to set the size of the thumb - if (m_thumb->renderer()->style()->hasAppearance()) - theme()->adjustSliderThumbSize(m_thumb->renderer()); + RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0; + + IntSize baseSize(borderLeft() + paddingLeft() + paddingRight() + borderRight(), + borderTop() + paddingTop() + paddingBottom() + borderBottom()); + + if (thumb) { + // Allow the theme to set the size of the thumb. + if (thumb->style()->hasAppearance()) { + // FIXME: This should pass the style, not the renderer, to the theme. + theme()->adjustSliderThumbSize(thumb); + } + + baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0)); + } + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + IntSize oldSize = size(); + + setSize(baseSize); + calcWidth(); + calcHeight(); + + IntRect overflowRect(IntPoint(), size()); + if (thumb) { + if (oldSize != size()) + thumb->setChildNeedsLayout(true, false); + + LayoutStateMaintainer statePusher(view(), this, size()); + + IntRect oldThumbRect = thumb->frameRect(); + + thumb->layoutIfNeeded(); + + IntRect thumbRect; + + thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth())); + thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight())); + + double fraction = sliderPosition(static_cast<HTMLInputElement*>(node())); + IntRect contentRect = contentBoxRect(); if (style()->appearance() == SliderVerticalPart) { - // FIXME: Handle percentage widths correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104 - m_thumb->renderer()->style()->setLeft(Length(contentWidth() / 2 - m_thumb->renderer()->style()->width().value() / 2, Fixed)); + thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2); + thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction))); } else { - // FIXME: Handle percentage heights correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104 - m_thumb->renderer()->style()->setTop(Length(contentHeight() / 2 - m_thumb->renderer()->style()->height().value() / 2, Fixed)); + thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction)); + thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2); } - if (relayoutChildren) - setPositionFromValue(true); + thumb->setFrameRect(thumbRect); + + if (thumb->checkForRepaintDuringLayout()) + thumb->repaintDuringLayoutIfMoved(oldThumbRect); + + statePusher.pop(); + + IntRect thumbOverflowRect = thumb->overflowRect(); + thumbOverflowRect.move(thumb->x(), thumb->y()); + overflowRect.unite(thumbOverflowRect); } - RenderBlock::layoutBlock(relayoutChildren); + // FIXME: m_overflowWidth and m_overflowHeight should be renamed + // m_overflowRight and m_overflowBottom. + m_overflowLeft = overflowRect.x(); + m_overflowTop = overflowRect.y(); + m_overflowWidth = overflowRect.right(); + m_overflowHeight = overflowRect.bottom(); + + repainter.repaintAfterLayout(); + + setNeedsLayout(false); } void RenderSlider::updateFromElement() { + HTMLInputElement* element = static_cast<HTMLInputElement*>(node()); + + // Send the value back to the element if the range changes it. + SliderRange range(element); + bool clamped; + double value = range.valueFromElement(element, &clamped); + if (clamped) + element->setValueFromRenderer(String::number(value)); + + // Layout will take care of the thumb's size and position. if (!m_thumb) { - m_thumb = new HTMLSliderThumbElement(document(), node()); + m_thumb = new SliderThumbElement(document(), node()); RefPtr<RenderStyle> thumbStyle = createThumbStyle(style()); m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get())); m_thumb->renderer()->setStyle(thumbStyle.release()); @@ -267,8 +371,7 @@ void RenderSlider::updateFromElement() m_thumb->setInDocument(true); addChild(m_thumb->renderer()); } - setPositionFromValue(); - setNeedsLayout(true, false); + setNeedsLayout(true); } bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt) @@ -279,92 +382,45 @@ bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt) #if ENABLE(VIDEO) if (style()->appearance() == MediaSliderPart) { MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node()); - IntPoint absPoint(evt->pageX(), evt->pageY()); - return sliderThumb->hitTest(absPoint); - } else -#endif - { - FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(FloatPoint(evt->pageX(), evt->pageY()), false, true); - IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect(); - return thumbBounds.contains(roundedIntPoint(localPoint)); + return sliderThumb->hitTest(evt->absoluteLocation()); } +#endif + + FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true); + IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect(); + return thumbBounds.contains(roundedIntPoint(localPoint)); } void RenderSlider::setValueForPosition(int position) { if (!m_thumb || !m_thumb->renderer()) return; - - const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr); - const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr); - const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr); - - double minVal = minStr.isNull() ? 0.0 : minStr.toDouble(); - double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble(); - minVal = min(minVal, maxVal); // Make sure the range is sane. - - // Calculate the new value based on the position - double factor = (double)position / (double)trackSize(); - if (style()->appearance() == SliderVerticalPart) - factor = 1.0 - factor; - double val = minVal + factor * (maxVal - minVal); - - val = max(minVal, min(val, maxVal)); // Make sure val is within min/max. - // Force integer value if not float. - if (!equalIgnoringCase(precision, "float")) - val = lround(val); + HTMLInputElement* element = static_cast<HTMLInputElement*>(node()); - static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val)); - - if (position != currentPosition()) { - setCurrentPosition(position); - static_cast<HTMLInputElement*>(node())->onChange(); - } -} - -double RenderSlider::setPositionFromValue(bool inLayout) -{ - if (!m_thumb || !m_thumb->renderer()) - return 0; - - if (!inLayout) - document()->updateLayout(); - - String value = static_cast<HTMLInputElement*>(node())->value(); - const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr); - const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr); - const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr); - - double minVal = minStr.isNull() ? 0.0 : minStr.toDouble(); - double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble(); - minVal = min(minVal, maxVal); // Make sure the range is sane. - - double oldVal = value.isNull() ? (maxVal + minVal)/2.0 : value.toDouble(); - double val = max(minVal, min(oldVal, maxVal)); // Make sure val is within min/max. - - // Force integer value if not float. - if (!equalIgnoringCase(precision, "float")) - val = lround(val); - - // Calculate the new position based on the value - double factor = (val - minVal) / (maxVal - minVal); + // Calculate the new value based on the position, and send it to the element. + SliderRange range(element); + double fraction = static_cast<double>(position) / trackSize(); if (style()->appearance() == SliderVerticalPart) - factor = 1.0 - factor; + fraction = 1 - fraction; + double value = range.clampValue(range.valueFromProportion(fraction)); + element->setValueFromRenderer(String::number(value)); - setCurrentPosition((int)(factor * trackSize())); - - if (value.isNull() || val != oldVal) - static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val)); - - return val; + // Also update the position if appropriate. + if (position != currentPosition()) { + setNeedsLayout(true); + + // FIXME: It seems like this could send extra change events if the same value is set + // multiple times with no layout in between. + element->onChange(); + } } int RenderSlider::positionForOffset(const IntPoint& p) { if (!m_thumb || !m_thumb->renderer()) return 0; - + int position; if (style()->appearance() == SliderVerticalPart) position = p.y() - m_thumb->renderBox()->height() / 2; @@ -374,55 +430,44 @@ int RenderSlider::positionForOffset(const IntPoint& p) return max(0, min(position, trackSize())); } -void RenderSlider::valueChanged() -{ - setValueForPosition(currentPosition()); - static_cast<HTMLInputElement*>(node())->onChange(); -} - int RenderSlider::currentPosition() { - if (!m_thumb || !m_thumb->renderer()) - return 0; - - if (style()->appearance() == SliderVerticalPart) - return m_thumb->renderer()->style()->top().value(); - return m_thumb->renderer()->style()->left().value(); -} - -void RenderSlider::setCurrentPosition(int pos) -{ - if (!m_thumb || !m_thumb->renderer()) - return; + ASSERT(m_thumb); + ASSERT(m_thumb->renderer()); if (style()->appearance() == SliderVerticalPart) - m_thumb->renderer()->style()->setTop(Length(pos, Fixed)); - else - m_thumb->renderer()->style()->setLeft(Length(pos, Fixed)); - - m_thumb->renderBox()->layer()->updateLayerPosition(); - repaint(); - m_thumb->renderer()->repaint(); + return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y(); + return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x(); } int RenderSlider::trackSize() { - if (!m_thumb || !m_thumb->renderer()) - return 0; + ASSERT(m_thumb); + ASSERT(m_thumb->renderer()); if (style()->appearance() == SliderVerticalPart) return contentHeight() - m_thumb->renderBox()->height(); return contentWidth() - m_thumb->renderBox()->width(); } -void RenderSlider::forwardEvent(Event* evt) +void RenderSlider::forwardEvent(Event* event) { - m_thumb->defaultEventHandler(evt); + if (event->isMouseEvent()) { + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) { + if (!mouseEventIsInThumb(mouseEvent)) { + IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); + setValueForPosition(positionForOffset(eventOffset)); + } + } + } + + m_thumb->defaultEventHandler(event); } bool RenderSlider::inDragMode() const { - return m_thumb->inDragMode(); + return m_thumb && m_thumb->inDragMode(); } } // namespace WebCore |