42
44
using namespace HTMLNames;
44
const int defaultTrackLength = 129;
46
class HTMLSliderThumbElement : public HTMLDivElement {
46
static const int defaultTrackLength = 129;
48
// FIXME: The SliderRange class and functions are entirely based on the DOM,
49
// and could be put with HTMLInputElement (possibly with a new name) instead of here.
55
explicit SliderRange(HTMLInputElement*);
56
double clampValue(double value);
58
// Map value into 0-1 range
59
double proportionFromValue(double value)
61
if (minimum == maximum)
64
return (value - minimum) / (maximum - minimum);
67
// Map from 0-1 range to value
68
double valueFromProportion(double proportion)
70
return minimum + proportion * (maximum - minimum);
73
double valueFromElement(HTMLInputElement*, bool* wasClamped = 0);
76
SliderRange::SliderRange(HTMLInputElement* element)
78
// FIXME: What's the right way to handle an integral range with non-integral minimum and maximum?
79
// Currently values are guaranteed to be integral but could be outside the range in that case.
81
isIntegral = !equalIgnoringCase(element->getAttribute(precisionAttr), "float");
83
// FIXME: This treats maximum strings that can't be parsed as 0, but perhaps 100 would be more appropriate.
84
const AtomicString& maxString = element->getAttribute(maxAttr);
85
maximum = maxString.isNull() ? 100.0 : maxString.toDouble();
87
// If the maximum is smaller, use it as the minimum.
88
minimum = min(element->getAttribute(minAttr).toDouble(), maximum);
91
double SliderRange::clampValue(double value)
93
double clampedValue = max(minimum, min(value, maximum));
94
return isIntegral ? round(clampedValue) : clampedValue;
97
double SliderRange::valueFromElement(HTMLInputElement* element, bool* wasClamped)
99
String valueString = element->value();
100
double oldValue = valueString.isNull() ? (minimum + maximum) / 2 : valueString.toDouble();
101
double newValue = clampValue(oldValue);
104
*wasClamped = valueString.isNull() || newValue != oldValue;
109
// Returns a value between 0 and 1.
110
// As with SliderRange, this could be on HTMLInputElement instead of here.
111
static double sliderPosition(HTMLInputElement* element)
113
SliderRange range(element);
114
return range.proportionFromValue(range.valueFromElement(element));
117
class SliderThumbElement : public HTMLDivElement {
48
HTMLSliderThumbElement(Document*, Node* shadowParent = 0);
119
SliderThumbElement(Document*, Node* shadowParent);
121
bool inDragMode() const { return m_inDragMode; }
50
123
virtual void defaultEventHandler(Event*);
124
virtual void detach();
51
127
virtual bool isShadowNode() const { return true; }
52
128
virtual Node* shadowParentNode() { return m_shadowParent; }
54
bool inDragMode() const { return m_inDragMode; }
130
FloatPoint m_offsetToThumb;
56
131
Node* m_shadowParent;
57
FloatPoint m_initialClickPoint; // initial click point in RenderSlider-local coordinates
58
int m_initialPosition;
59
132
bool m_inDragMode;
62
HTMLSliderThumbElement::HTMLSliderThumbElement(Document* doc, Node* shadowParent)
63
: HTMLDivElement(divTag, doc)
135
SliderThumbElement::SliderThumbElement(Document* document, Node* shadowParent)
136
: HTMLDivElement(divTag, document)
64
137
, m_shadowParent(shadowParent)
65
, m_initialClickPoint(IntPoint())
66
, m_initialPosition(0)
67
138
, m_inDragMode(false)
71
void HTMLSliderThumbElement::defaultEventHandler(Event* event)
142
void SliderThumbElement::defaultEventHandler(Event* event)
144
if (!event->isMouseEvent()) {
145
HTMLDivElement::defaultEventHandler(event);
149
MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
150
bool isLeftButton = mouseEvent->button() == LeftButton;
73
151
const AtomicString& eventType = event->type();
74
if (eventType == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
75
MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
77
if (document()->frame() && renderer() && renderer()->parent() &&
78
(slider = static_cast<RenderSlider*>(renderer()->parent())) &&
79
slider->mouseEventIsInThumb(mouseEvent)) {
80
// Cache the initial point where the mouse down occurred, in slider coordinates
81
m_initialClickPoint = slider->absoluteToLocal(FloatPoint(mouseEvent->pageX(), mouseEvent->pageY()), false, true);
82
// Cache the initial position of the thumb.
83
m_initialPosition = slider->currentPosition();
86
document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
88
event->setDefaultHandled();
153
if (eventType == eventNames().mousedownEvent && isLeftButton) {
154
if (document()->frame() && renderer()) {
155
RenderSlider* slider = toRenderSlider(renderer()->parent());
157
if (slider->mouseEventIsInThumb(mouseEvent)) {
158
// We selected the thumb, we want the cursor to always stay at
159
// the same position relative to the thumb.
160
m_offsetToThumb = slider->mouseEventOffsetToThumb(mouseEvent);
162
// We are outside the thumb, move the thumb to the point were
163
// we clicked. We'll be exactly at the center of the thumb.
164
m_offsetToThumb.setX(0);
165
m_offsetToThumb.setY(0);
169
document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
170
event->setDefaultHandled();
91
} else if (eventType == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
174
} else if (eventType == eventNames().mouseupEvent && isLeftButton) {
92
175
if (m_inDragMode) {
93
176
if (Frame* frame = document()->frame())
94
177
frame->eventHandler()->setCapturingMouseEventsNode(0);
96
179
event->setDefaultHandled();
99
} else if (eventType == eventNames().mousemoveEvent && event->isMouseEvent()) {
182
} else if (eventType == eventNames().mousemoveEvent) {
100
183
if (m_inDragMode && renderer() && renderer()->parent()) {
102
MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
103
RenderSlider* slider = static_cast<RenderSlider*>(renderer()->parent());
104
FloatPoint curPoint = slider->absoluteToLocal(FloatPoint(mouseEvent->pageX(), mouseEvent->pageY()), false, true);
105
int newPosition = slider->positionForOffset(
106
IntPoint(m_initialPosition + curPoint.x() - m_initialClickPoint.x()
107
+ (renderer()->width() / 2),
108
m_initialPosition + curPoint.y() - m_initialClickPoint.y()
109
+ (renderer()->height() / 2)));
110
if (slider->currentPosition() != newPosition) {
111
slider->setCurrentPosition(newPosition);
112
slider->valueChanged();
184
RenderSlider* slider = toRenderSlider(renderer()->parent());
186
FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
187
IntPoint eventOffset(curPoint.x() + m_offsetToThumb.x(), curPoint.y() + m_offsetToThumb.y());
188
slider->setValueForPosition(slider->positionForOffset(eventOffset));
189
event->setDefaultHandled();
114
event->setDefaultHandled();
119
195
HTMLDivElement::defaultEventHandler(event);
198
void SliderThumbElement::detach()
201
if (Frame* frame = document()->frame())
202
frame->eventHandler()->setCapturingMouseEventsNode(0);
204
HTMLDivElement::detach();
122
207
RenderSlider::RenderSlider(HTMLInputElement* element)
123
208
: RenderBlock(element)
190
274
style->inheritFrom(parentStyle);
192
276
style->setDisplay(BLOCK);
193
style->setPosition(RelativePosition);
195
style->setLeft(oldStyle->left());
196
style->setTop(oldStyle->top());
199
278
if (parentStyle->appearance() == SliderVerticalPart)
200
style->setAppearance(SliderThumbVerticalPart);
279
style->setAppearance(SliderThumbVerticalPart);
201
280
else if (parentStyle->appearance() == SliderHorizontalPart)
202
style->setAppearance(SliderThumbHorizontalPart);
281
style->setAppearance(SliderThumbHorizontalPart);
203
282
else if (parentStyle->appearance() == MediaSliderPart)
204
283
style->setAppearance(MediaSliderThumbPart);
284
else if (parentStyle->appearance() == MediaVolumeSliderPart)
285
style->setAppearance(MediaVolumeSliderThumbPart);
206
287
return style.release();
290
IntRect RenderSlider::thumbRect()
296
RenderBox* thumb = toRenderBox(m_thumb->renderer());
298
thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
299
thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
301
double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
302
IntRect contentRect = contentBoxRect();
303
if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) {
304
thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
305
thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
307
thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
308
thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
209
314
void RenderSlider::layout()
211
bool relayoutChildren = false;
213
if (m_thumb && m_thumb->renderer()) {
215
int oldWidth = m_width;
217
int oldHeight = m_height;
220
if (oldWidth != m_width || oldHeight != m_height)
221
relayoutChildren = true;
223
// Allow the theme to set the size of the thumb
224
if (m_thumb->renderer()->style()->hasAppearance())
225
theme()->adjustSliderThumbSize(m_thumb->renderer());
227
if (style()->appearance() == SliderVerticalPart) {
228
// FIXME: Handle percentage widths correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
229
m_thumb->renderer()->style()->setLeft(Length(contentWidth() / 2 - m_thumb->renderer()->style()->width().value() / 2, Fixed));
231
// FIXME: Handle percentage heights correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
232
m_thumb->renderer()->style()->setTop(Length(contentHeight() / 2 - m_thumb->renderer()->style()->height().value() / 2, Fixed));
316
ASSERT(needsLayout());
318
RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0;
320
IntSize baseSize(borderLeft() + paddingLeft() + paddingRight() + borderRight(),
321
borderTop() + paddingTop() + paddingBottom() + borderBottom());
324
// Allow the theme to set the size of the thumb.
325
if (thumb->style()->hasAppearance()) {
326
// FIXME: This should pass the style, not the renderer, to the theme.
327
theme()->adjustSliderThumbSize(thumb);
235
if (relayoutChildren)
236
setPositionFromValue(true);
239
RenderBlock::layoutBlock(relayoutChildren);
330
baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
333
LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
335
IntSize oldSize = size();
342
if (oldSize != size())
343
thumb->setChildNeedsLayout(true, false);
345
LayoutStateMaintainer statePusher(view(), this, size());
347
IntRect oldThumbRect = thumb->frameRect();
349
thumb->layoutIfNeeded();
351
IntRect rect = thumbRect();
352
thumb->setFrameRect(rect);
353
if (thumb->checkForRepaintDuringLayout())
354
thumb->repaintDuringLayoutIfMoved(oldThumbRect);
359
addOverflowFromChild(thumb);
361
repainter.repaintAfterLayout();
363
setNeedsLayout(false);
242
366
void RenderSlider::updateFromElement()
368
HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
370
// Send the value back to the element if the range changes it.
371
SliderRange range(element);
373
double value = range.valueFromElement(element, &clamped);
375
element->setValueFromRenderer(String::number(value));
377
// Layout will take care of the thumb's size and position.
245
m_thumb = new HTMLSliderThumbElement(document(), node());
379
m_thumb = new SliderThumbElement(document(), node());
246
380
RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
247
381
m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
248
382
m_thumb->renderer()->setStyle(thumbStyle.release());
250
384
m_thumb->setInDocument(true);
251
385
addChild(m_thumb->renderer());
253
setPositionFromValue();
254
setNeedsLayout(true, false);
387
setNeedsLayout(true);
257
390
bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
259
392
if (!m_thumb || !m_thumb->renderer())
262
FloatPoint localPoint = m_thumb->renderer()->absoluteToLocal(FloatPoint(evt->pageX(), evt->pageY()), false, true);
263
IntRect thumbBounds = m_thumb->renderer()->borderBox();
396
if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) {
397
MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
398
return sliderThumb->hitTest(evt->absoluteLocation());
402
FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
403
IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
264
404
return thumbBounds.contains(roundedIntPoint(localPoint));
407
FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
409
ASSERT(m_thumb && m_thumb->renderer());
410
FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
411
IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
413
offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
414
offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
267
418
void RenderSlider::setValueForPosition(int position)
269
420
if (!m_thumb || !m_thumb->renderer())
272
const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
273
const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
274
const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
276
double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
277
double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
278
minVal = min(minVal, maxVal); // Make sure the range is sane.
280
// Calculate the new value based on the position
281
double factor = (double)position / (double)trackSize();
282
if (style()->appearance() == SliderVerticalPart)
283
factor = 1.0 - factor;
284
double val = minVal + factor * (maxVal - minVal);
286
val = max(minVal, min(val, maxVal)); // Make sure val is within min/max.
288
// Force integer value if not float.
289
if (!equalIgnoringCase(precision, "float"))
292
static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
423
HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
425
// Calculate the new value based on the position, and send it to the element.
426
SliderRange range(element);
427
double fraction = static_cast<double>(position) / trackSize();
428
if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
429
fraction = 1 - fraction;
430
double value = range.clampValue(range.valueFromProportion(fraction));
431
element->setValueFromRenderer(String::number(value));
433
// Also update the position if appropriate.
294
434
if (position != currentPosition()) {
295
setCurrentPosition(position);
296
static_cast<HTMLInputElement*>(node())->onChange();
435
setNeedsLayout(true);
437
// FIXME: It seems like this could send extra change events if the same value is set
438
// multiple times with no layout in between.
439
element->dispatchFormControlChangeEvent();
300
double RenderSlider::setPositionFromValue(bool inLayout)
302
if (!m_thumb || !m_thumb->renderer())
306
document()->updateLayout();
308
String value = static_cast<HTMLInputElement*>(node())->value();
309
const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
310
const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
311
const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
313
double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
314
double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
315
minVal = min(minVal, maxVal); // Make sure the range is sane.
317
double oldVal = value.isNull() ? (maxVal + minVal)/2.0 : value.toDouble();
318
double val = max(minVal, min(oldVal, maxVal)); // Make sure val is within min/max.
320
// Force integer value if not float.
321
if (!equalIgnoringCase(precision, "float"))
324
// Calculate the new position based on the value
325
double factor = (val - minVal) / (maxVal - minVal);
326
if (style()->appearance() == SliderVerticalPart)
327
factor = 1.0 - factor;
329
setCurrentPosition((int)(factor * trackSize()));
331
if (value.isNull() || val != oldVal)
332
static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
337
443
int RenderSlider::positionForOffset(const IntPoint& p)
339
445
if (!m_thumb || !m_thumb->renderer())
343
if (style()->appearance() == SliderVerticalPart)
344
position = p.y() - m_thumb->renderer()->height() / 2;
449
if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
450
position = p.y() - m_thumb->renderBox()->height() / 2;
346
position = p.x() - m_thumb->renderer()->width() / 2;
452
position = p.x() - m_thumb->renderBox()->width() / 2;
348
454
return max(0, min(position, trackSize()));
351
void RenderSlider::valueChanged()
353
setValueForPosition(currentPosition());
354
static_cast<HTMLInputElement*>(node())->onChange();
357
457
int RenderSlider::currentPosition()
359
if (!m_thumb || !m_thumb->renderer())
362
if (style()->appearance() == SliderVerticalPart)
363
return m_thumb->renderer()->style()->top().value();
364
return m_thumb->renderer()->style()->left().value();
367
void RenderSlider::setCurrentPosition(int pos)
369
if (!m_thumb || !m_thumb->renderer())
372
if (style()->appearance() == SliderVerticalPart)
373
m_thumb->renderer()->style()->setTop(Length(pos, Fixed));
375
m_thumb->renderer()->style()->setLeft(Length(pos, Fixed));
377
m_thumb->renderer()->layer()->updateLayerPosition();
379
m_thumb->renderer()->repaint();
460
ASSERT(m_thumb->renderer());
462
if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
463
return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y();
464
return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x();
382
467
int RenderSlider::trackSize()
384
if (!m_thumb || !m_thumb->renderer())
470
ASSERT(m_thumb->renderer());
387
if (style()->appearance() == SliderVerticalPart)
388
return contentHeight() - m_thumb->renderer()->height();
389
return contentWidth() - m_thumb->renderer()->width();
472
if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
473
return contentHeight() - m_thumb->renderBox()->height();
474
return contentWidth() - m_thumb->renderBox()->width();
392
void RenderSlider::forwardEvent(Event* evt)
477
void RenderSlider::forwardEvent(Event* event)
394
m_thumb->defaultEventHandler(evt);
479
if (event->isMouseEvent()) {
480
MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
481
if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
482
if (!mouseEventIsInThumb(mouseEvent)) {
483
IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
484
setValueForPosition(positionForOffset(eventOffset));
489
m_thumb->defaultEventHandler(event);
397
492
bool RenderSlider::inDragMode() const
399
return m_thumb->inDragMode();
494
return m_thumb && m_thumb->inDragMode();
402
497
} // namespace WebCore