diff --git a/layout/forms/nsRangeFrame.cpp b/layout/forms/nsRangeFrame.cpp index 7ffc361d2a23..1e66416e48b8 100644 --- a/layout/forms/nsRangeFrame.cpp +++ b/layout/forms/nsRangeFrame.cpp @@ -242,63 +242,26 @@ nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext, nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); if (thumbFrame) { // display:none? - - // Position the thumb: - // The idea here is that we want to position the thumb so that the center - // of the thumb is on an imaginary line drawn from the middle of one edge - // of the range frame's content box to the middle of the opposite edge of - // its content box (the opposite edges being the left/right edge if the - // range is horizontal, or else the top/bottom edges if the range is - // vertical). How far along this line the center of the thumb is placed - // depends on the value of the range. - - nsSize frameSizeOverride(aDesiredSize.width, aDesiredSize.height); - bool isHorizontal = IsHorizontal(&frameSizeOverride); - - double valueAsFraction = GetValueAsFractionOfRange(); - MOZ_ASSERT(valueAsFraction >= 0.0 && valueAsFraction <= 1.0); - nsHTMLReflowState thumbReflowState(aPresContext, aReflowState, thumbFrame, nsSize(aReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE)); - // Find the x/y position of the thumb frame such that it will be positioned - // as described above. These coordinates are with respect to the - // nsRangeFrame's border-box. - nscoord thumbX, thumbY; - - if (isHorizontal) { - thumbX = NSToCoordRound(rangeFrameContentBoxWidth * valueAsFraction); - if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { - thumbX = rangeFrameContentBoxWidth - thumbX; - } - thumbY = rangeFrameContentBoxHeight / 2; - } else { - thumbX = rangeFrameContentBoxWidth / 2; - // For vertical range zero is at the bottom, so subtract from height: - thumbY = rangeFrameContentBoxHeight - - NSToCoordRound(rangeFrameContentBoxHeight * valueAsFraction); - } - - thumbX -= thumbReflowState.mComputedBorderPadding.left + - thumbReflowState.ComputedWidth() / 2; - thumbY -= thumbReflowState.mComputedBorderPadding.top + - thumbReflowState.ComputedHeight() / 2; - - // Make relative to our border box instead of our content box: - thumbX += aReflowState.mComputedBorderPadding.left; - thumbY += aReflowState.mComputedBorderPadding.top; + // Where we position of the thumb depends on its size, so we first reflow + // the thumb at {0,0} to obtain its size, then position it afterwards. nsReflowStatus frameStatus = NS_FRAME_COMPLETE; nsHTMLReflowMetrics thumbDesiredSize; nsresult rv = ReflowChild(thumbFrame, aPresContext, thumbDesiredSize, - thumbReflowState, thumbX, thumbY, 0, frameStatus); + thumbReflowState, 0, 0, 0, frameStatus); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus), "We gave our child unconstrained height, so it should be complete"); rv = FinishReflowChild(thumbFrame, aPresContext, &thumbReflowState, - thumbDesiredSize, thumbX, thumbY, 0); + thumbDesiredSize, 0, 0, 0); NS_ENSURE_SUCCESS(rv, rv); + + DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.width, + aDesiredSize.height)); } return NS_OK; @@ -348,34 +311,57 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent) MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) && MOZ_DOUBLE_IS_FINITE(maximum), "type=range should have a default maximum/minimum"); - nsRect contentRect = GetContentRectRelativeToSelf(); - if (maximum <= minimum || - (IsHorizontal() && contentRect.width <= 0) || - (!IsHorizontal() && contentRect.height <= 0)) { + if (maximum <= minimum) { return minimum; } - double range = maximum - minimum; - nsIntPoint point; + nsIntPoint absPoint; if (aEvent->eventStructType == NS_TOUCH_EVENT) { MOZ_ASSERT(static_cast(aEvent)->touches.Length() == 1, "Unexpected number of touches"); - point = static_cast(aEvent)->touches[0]->mRefPoint; + absPoint = static_cast(aEvent)->touches[0]->mRefPoint; } else { - point = aEvent->refPoint; + absPoint = aEvent->refPoint; + } + nsPoint point = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, absPoint, this); + + nsRect rangeContentRect = GetContentRectRelativeToSelf(); + nsSize thumbSize; + nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); + if (thumbFrame) { // diplay:none? + thumbSize = thumbFrame->GetSize(); } - nsMargin borderAndPadding = GetUsedBorderAndPadding(); - nsPoint contentPoint = - nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, point, this) - - nsPoint(borderAndPadding.left, borderAndPadding.top); - contentPoint.x = mozilla::clamped(contentPoint.x, 0, contentRect.width); - contentPoint.y = mozilla::clamped(contentPoint.y, 0, contentRect.height); - if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { - return maximum - (double(contentPoint.x) / double(contentRect.width)) * range; + double fraction; + if (IsHorizontal()) { + nscoord traversableDistance = rangeContentRect.width - thumbSize.width; + if (traversableDistance <= 0) { + return minimum; + } + nscoord posAtStart = rangeContentRect.x + thumbSize.width/2; + nscoord posAtEnd = posAtStart + traversableDistance; + nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd); + fraction = (posOfPoint - posAtStart) / double(traversableDistance); + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + fraction = 1.0 - fraction; + } + } else { + nscoord traversableDistance = rangeContentRect.height - thumbSize.height; + if (traversableDistance <= 0) { + return minimum; + } + nscoord posAtStart = rangeContentRect.y + thumbSize.height/2; + nscoord posAtEnd = posAtStart + traversableDistance; + nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd); + // For a vertical range, the top (posAtStart) is the highest value, so we + // subtract the fraction from 1.0 to get that polarity correct. + fraction = 1.0 - (posOfPoint - posAtStart) / double(traversableDistance); } - return minimum + (double(contentPoint.x) / double(contentRect.width)) * range; + + MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); + return minimum + fraction * range; } void @@ -388,25 +374,59 @@ nsRangeFrame::UpdateThumbPositionForValueChange() if (!thumbFrame) { return; // diplay:none? } - // TODO in bug 842179 - factor out duplication here and in Reflow. - double fraction = GetValueAsFractionOfRange(); - nsRect contentRect = GetContentRectRelativeToSelf(); - nsMargin borderAndPadding = GetUsedBorderAndPadding(); - nsSize thumbSize = thumbFrame->GetSize(); - nsPoint newPosition(borderAndPadding.left, borderAndPadding.top); - if (IsHorizontal()) { - newPosition += nsPoint(NSToCoordRound(fraction * contentRect.width) - - thumbSize.width/2, - (contentRect.height - thumbSize.height)/2); - } else { - newPosition += nsPoint((contentRect.width - thumbSize.width)/2, - NSToCoordRound(fraction * contentRect.height) - - thumbSize.height/2); - } - thumbFrame->SetPosition(newPosition); + DoUpdateThumbPosition(thumbFrame, GetSize()); SchedulePaint(); } +void +nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame, + const nsSize& aRangeSize) +{ + MOZ_ASSERT(aThumbFrame); + + // The idea here is that we want to position the thumb so that the center + // of the thumb is on an imaginary line drawn from the middle of one edge + // of the range frame's content box to the middle of the opposite edge of + // its content box (the opposite edges being the left/right edge if the + // range is horizontal, or else the top/bottom edges if the range is + // vertical). How far along this line the center of the thumb is placed + // depends on the value of the range. + + nsMargin borderAndPadding = GetUsedBorderAndPadding(); + nsPoint newPosition(borderAndPadding.left, borderAndPadding.top); + + nsSize rangeContentBoxSize(aRangeSize); + rangeContentBoxSize.width -= borderAndPadding.LeftRight(); + rangeContentBoxSize.height -= borderAndPadding.TopBottom(); + + nsSize thumbSize = aThumbFrame->GetSize(); + double fraction = GetValueAsFractionOfRange(); + MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); + + // We are called under Reflow, so we need to pass IsHorizontal a valid rect. + nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height); + if (IsHorizontal(&frameSizeOverride)) { + if (thumbSize.width < rangeContentBoxSize.width) { + nscoord traversableDistance = + rangeContentBoxSize.width - thumbSize.width; + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance); + } else { + newPosition.x += NSToCoordRound(fraction * traversableDistance); + } + newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2; + } + } else { + if (thumbSize.height < rangeContentBoxSize.height) { + nscoord traversableDistance = + rangeContentBoxSize.height - thumbSize.height; + newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2; + newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance); + } + } + aThumbFrame->SetPosition(newPosition); +} + NS_IMETHODIMP nsRangeFrame::AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, diff --git a/layout/forms/nsRangeFrame.h b/layout/forms/nsRangeFrame.h index 442a70aa6674..ed35b9db7a6f 100644 --- a/layout/forms/nsRangeFrame.h +++ b/layout/forms/nsRangeFrame.h @@ -106,6 +106,9 @@ private: nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState); + void DoUpdateThumbPosition(nsIFrame* aThumbFrame, + const nsSize& aRangeSize); + /** * Returns the input element's value as a fraction of the difference between * the input's minimum and its maximum (i.e. returns 0.0 when the value is