The 'global isinf/isnan' compiler quirk required when using clang with libstdc++
[WebKit.git] / Source / WebCore / rendering / RenderMediaControlsChromium.cpp
1 /*
2  * Copyright (C) 2009 Apple Inc.
3  * Copyright (C) 2009 Google Inc.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "RenderMediaControlsChromium.h"
30
31 #include "Gradient.h"
32 #include "GraphicsContext.h"
33 #include "HTMLMediaElement.h"
34 #include "HTMLNames.h"
35 #include "PaintInfo.h"
36 #include "TimeRanges.h"
37
38 namespace WebCore {
39
40 #if ENABLE(VIDEO)
41
42 typedef WTF::HashMap<const char*, Image*> MediaControlImageMap;
43 static MediaControlImageMap* gMediaControlImageMap = 0;
44
45 static Image* platformResource(const char* name)
46 {
47     if (!gMediaControlImageMap)
48         gMediaControlImageMap = new MediaControlImageMap();
49     if (Image* image = gMediaControlImageMap->get(name))
50         return image;
51     if (Image* image = Image::loadPlatformResource(name).leakRef()) {
52         gMediaControlImageMap->set(name, image);
53         return image;
54     }
55     ASSERT_NOT_REACHED();
56     return 0;
57 }
58
59 static bool hasSource(const HTMLMediaElement* mediaElement)
60 {
61     return mediaElement->networkState() != HTMLMediaElement::NETWORK_EMPTY
62         && mediaElement->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE;
63 }
64
65 static bool paintMediaButton(GraphicsContext* context, const IntRect& rect, Image* image)
66 {
67     context->drawImage(image, ColorSpaceDeviceRGB, rect);
68     return true;
69 }
70
71 static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
72 {
73     HTMLMediaElement* mediaElement = toParentMediaElement(object);
74     if (!mediaElement)
75       return false;
76
77     static Image* soundLevel3 = platformResource("mediaplayerSoundLevel3");
78     static Image* soundLevel2 = platformResource("mediaplayerSoundLevel2");
79     static Image* soundLevel1 = platformResource("mediaplayerSoundLevel1");
80     static Image* soundLevel0 = platformResource("mediaplayerSoundLevel0");
81     static Image* soundDisabled = platformResource("mediaplayerSoundDisabled");
82
83     if (!hasSource(mediaElement) || !mediaElement->hasAudio())
84         return paintMediaButton(paintInfo.context, rect, soundDisabled);
85
86     if (mediaElement->muted() || mediaElement->volume() <= 0)
87         return paintMediaButton(paintInfo.context, rect, soundLevel0);
88
89     if (mediaElement->volume() <= 0.33)
90         return paintMediaButton(paintInfo.context, rect, soundLevel1);
91
92     if (mediaElement->volume() <= 0.66)
93         return paintMediaButton(paintInfo.context, rect, soundLevel2);
94
95     return paintMediaButton(paintInfo.context, rect, soundLevel3);
96 }
97
98 static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
99 {
100     HTMLMediaElement* mediaElement = toParentMediaElement(object);
101     if (!mediaElement)
102         return false;
103
104     static Image* mediaPlay = platformResource("mediaplayerPlay");
105     static Image* mediaPause = platformResource("mediaplayerPause");
106     static Image* mediaPlayDisabled = platformResource("mediaplayerPlayDisabled");
107
108     if (!hasSource(mediaElement))
109         return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled);
110
111     return paintMediaButton(paintInfo.context, rect, mediaElement->canPlay() ? mediaPlay : mediaPause);
112 }
113
114 static bool paintMediaOverlayPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
115 {
116     HTMLMediaElement* mediaElement = toParentMediaElement(object);
117     if (!mediaElement)
118         return false;
119
120     if (!hasSource(mediaElement) || !mediaElement->canPlay())
121         return false;
122
123     static Image* mediaOverlayPlay = platformResource("mediaplayerOverlayPlay");
124     return paintMediaButton(paintInfo.context, rect, mediaOverlayPlay);
125 }
126
127 static Image* getMediaSliderThumb()
128 {
129     static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
130     return mediaSliderThumb;
131 }
132
133 static void paintRoundedSliderBackground(const IntRect& rect, const RenderStyle* style, GraphicsContext* context)
134 {
135     int borderRadius = rect.height() / 2;
136     IntSize radii(borderRadius, borderRadius);
137     Color sliderBackgroundColor = Color(11, 11, 11);
138     context->save();
139     context->fillRoundedRect(rect, radii, radii, radii, radii, sliderBackgroundColor, ColorSpaceDeviceRGB);
140     context->restore();
141 }
142
143 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor)
144 {
145     // Calculate border radius; need to avoid being smaller than half the slider height
146     // because of https://bugs.webkit.org/show_bug.cgi?id=30143.
147     int borderRadius = rect.height() / 2;
148     IntSize radii(borderRadius, borderRadius);
149
150     // Calculate highlight rectangle and edge dimensions.
151     int startOffset = startPosition;
152     int endOffset = rect.width() - endPosition;
153     int rangeWidth = endPosition - startPosition;
154
155     if (rangeWidth <= 0)
156         return;
157
158     // Make sure the range width is bigger than border radius at the edges to retain rounded corners.
159     if (startOffset < borderRadius && rangeWidth < borderRadius)
160         rangeWidth = borderRadius;
161     if (endOffset < borderRadius && rangeWidth < borderRadius) {
162         startPosition -= borderRadius - rangeWidth;
163         rangeWidth = borderRadius;
164     }
165
166     // Set rectangle to highlight range.
167     IntRect highlightRect = rect;
168     highlightRect.move(startOffset, 0);
169     highlightRect.setWidth(rangeWidth);
170
171     // Don't bother drawing an empty area.
172     if (highlightRect.isEmpty())
173         return;
174
175     // Calculate white-grey gradient.
176     IntPoint sliderTopLeft = highlightRect.location();
177     IntPoint sliderBottomLeft = sliderTopLeft;
178     sliderBottomLeft.move(0, highlightRect.height());
179     RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft);
180     gradient->addColorStop(0.0, startColor);
181     gradient->addColorStop(1.0, endColor);
182
183     // Fill highlight rectangle with gradient, potentially rounded if on left or right edge.
184     context->save();
185     context->setFillGradient(gradient);
186
187     if (startOffset < borderRadius && endOffset < borderRadius)
188         context->fillRoundedRect(highlightRect, radii, radii, radii, radii, startColor, ColorSpaceDeviceRGB);
189     else if (startOffset < borderRadius)
190         context->fillRoundedRect(highlightRect, radii, IntSize(0, 0), radii, IntSize(0, 0), startColor, ColorSpaceDeviceRGB);
191     else if (endOffset < borderRadius)
192         context->fillRoundedRect(highlightRect, IntSize(0, 0), radii, IntSize(0, 0), radii, startColor, ColorSpaceDeviceRGB);
193     else
194         context->fillRect(highlightRect);
195
196     context->restore();
197 }
198
199 const int mediaSliderThumbWidth = 32;
200
201 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
202 {
203     HTMLMediaElement* mediaElement = toParentMediaElement(object);
204     if (!mediaElement)
205         return false;
206
207     RenderStyle* style = object->style();
208     GraphicsContext* context = paintInfo.context;
209
210     paintRoundedSliderBackground(rect, style, context);
211
212     // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be
213     // distracting/'busy' to show all of them, show only the buffered range containing the current play head.
214     RefPtr<TimeRanges> bufferedTimeRanges = mediaElement->buffered();
215     float duration = mediaElement->duration();
216     float currentTime = mediaElement->currentTime();
217     if (std::isnan(duration) || std::isinf(duration) || !duration || std::isnan(currentTime))
218         return true;
219
220     for (unsigned i = 0; i < bufferedTimeRanges->length(); ++i) {
221         float start = bufferedTimeRanges->start(i, ASSERT_NO_EXCEPTION);
222         float end = bufferedTimeRanges->end(i, ASSERT_NO_EXCEPTION);
223         if (std::isnan(start) || std::isnan(end) || start > currentTime || end < currentTime)
224             continue;
225         int startPosition = int(start * rect.width() / duration);
226         int currentPosition = int(currentTime * rect.width() / duration);
227         int endPosition = int(end * rect.width() / duration);
228
229         // Add half the thumb width proportionally adjusted to the current painting position.
230         int thumbCenter = mediaSliderThumbWidth / 2;
231         int addWidth = thumbCenter * (1.0 - 2.0 * currentPosition / rect.width());
232         currentPosition += addWidth;
233
234         // Draw white-ish highlight before current time.
235         Color startColor = Color(195, 195, 195);
236         Color endColor = Color(217, 217, 217);
237         if (currentPosition > startPosition)
238             paintSliderRangeHighlight(rect, style, context, startPosition, currentPosition, startColor, endColor);
239
240         // Draw grey-ish highlight after current time.
241         startColor = Color(60, 60, 60);
242         endColor = Color(76, 76, 76);
243
244         if (endPosition > currentPosition)
245             paintSliderRangeHighlight(rect, style, context, currentPosition, endPosition, startColor, endColor);
246
247         return true;
248     }
249
250     return true;
251 }
252
253 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
254 {
255     ASSERT(object->node());
256     HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
257     if (!mediaElement)
258         return false;
259
260     if (!hasSource(mediaElement))
261         return true;
262
263     Image* mediaSliderThumb = getMediaSliderThumb();
264     return paintMediaButton(paintInfo.context, rect, mediaSliderThumb);
265 }
266
267 const int mediaVolumeSliderThumbWidth = 24;
268
269 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
270 {
271     HTMLMediaElement* mediaElement = toParentMediaElement(object);
272     if (!mediaElement)
273         return false;
274
275     GraphicsContext* context = paintInfo.context;
276     RenderStyle* style = object->style();
277
278     paintRoundedSliderBackground(rect, style, context);
279
280     // Calculate volume position for white background rectangle.
281     float volume = mediaElement->volume();
282     if (std::isnan(volume) || volume < 0)
283         return true;
284     if (volume > 1)
285         volume = 1;
286     if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted())
287         volume = 0;
288
289     // Calculate the position relative to the center of the thumb.
290     float fillWidth = 0;
291     if (volume > 0) {
292         float thumbCenter = mediaVolumeSliderThumbWidth / 2;
293         float zoomLevel = style->effectiveZoom();
294         float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter));
295         fillWidth = positionWidth + (zoomLevel * thumbCenter / 2);
296     }
297
298     Color startColor = Color(195, 195, 195);
299     Color endColor = Color(217, 217, 217);
300
301     paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor);
302
303     return true;
304 }
305
306 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
307 {
308     ASSERT(object->node());
309     HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
310     if (!mediaElement)
311         return false;
312
313     if (!hasSource(mediaElement) || !mediaElement->hasAudio())
314         return true;
315
316     static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
317     return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb);
318 }
319
320 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
321 {
322     HTMLMediaElement* mediaElement = toParentMediaElement(object);
323     if (!mediaElement)
324         return false;
325
326     static Image* mediaFullscreenButton = platformResource("mediaplayerFullscreen");
327     return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton);
328 }
329
330 static bool paintMediaClosedCaptionsButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
331 {
332     HTMLMediaElement* mediaElement = toParentMediaElement(object);
333     if (!mediaElement)
334         return false;
335
336     static Image* mediaClosedCaptionButton = platformResource("mediaplayerClosedCaption");
337     static Image* mediaClosedCaptionButtonDisabled = platformResource("mediaplayerClosedCaptionDisabled");
338
339     if (mediaElement->webkitClosedCaptionsVisible())
340         return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButton);
341
342     return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDisabled);
343 }
344
345
346 bool RenderMediaControlsChromium::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
347 {
348     switch (part) {
349     case MediaMuteButton:
350     case MediaUnMuteButton:
351         return paintMediaMuteButton(object, paintInfo, rect);
352     case MediaPauseButton:
353     case MediaPlayButton:
354         return paintMediaPlayButton(object, paintInfo, rect);
355     case MediaShowClosedCaptionsButton:
356         return paintMediaClosedCaptionsButton(object, paintInfo, rect);
357     case MediaSlider:
358         return paintMediaSlider(object, paintInfo, rect);
359     case MediaSliderThumb:
360         return paintMediaSliderThumb(object, paintInfo, rect);
361     case MediaVolumeSlider:
362         return paintMediaVolumeSlider(object, paintInfo, rect);
363     case MediaVolumeSliderThumb:
364         return paintMediaVolumeSliderThumb(object, paintInfo, rect);
365     case MediaEnterFullscreenButton:
366     case MediaExitFullscreenButton:
367         return paintMediaFullscreenButton(object, paintInfo, rect);
368     case MediaOverlayPlayButton:
369         return paintMediaOverlayPlayButton(object, paintInfo, rect);
370     case MediaVolumeSliderMuteButton:
371     case MediaSeekBackButton:
372     case MediaSeekForwardButton:
373     case MediaVolumeSliderContainer:
374     case MediaTimelineContainer:
375     case MediaCurrentTimeDisplay:
376     case MediaTimeRemainingDisplay:
377     case MediaControlsPanel:
378     case MediaRewindButton:
379     case MediaReturnToRealtimeButton:
380     case MediaStatusDisplay:
381     case MediaHideClosedCaptionsButton:
382     case MediaTextTrackDisplayContainer:
383     case MediaTextTrackDisplay:
384     case MediaFullScreenVolumeSlider:
385     case MediaFullScreenVolumeSliderThumb:
386     case MediaClosedCaptionsContainer:
387     case MediaClosedCaptionsTrackList:
388         ASSERT_NOT_REACHED();
389         break;
390     }
391     return false;
392 }
393
394 const int mediaSliderThumbHeight = 24;
395 const int mediaVolumeSliderThumbHeight = 24;
396
397 void RenderMediaControlsChromium::adjustMediaSliderThumbSize(RenderStyle* style)
398 {
399     static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
400     static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
401     int width = 0;
402     int height = 0;
403
404     Image* thumbImage = 0;
405     if (style->appearance() == MediaSliderThumbPart) {
406         thumbImage = mediaSliderThumb;
407         width = mediaSliderThumbWidth;
408         height = mediaSliderThumbHeight;
409     } else if (style->appearance() == MediaVolumeSliderThumbPart) {
410         thumbImage = mediaVolumeSliderThumb;
411         width = mediaVolumeSliderThumbWidth;
412         height = mediaVolumeSliderThumbHeight;
413     }
414
415     float zoomLevel = style->effectiveZoom();
416     if (thumbImage) {
417         style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed));
418         style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed));
419     }
420 }
421
422 static String formatChromiumMediaControlsTime(float time, float duration)
423 {
424     if (!isfinite(time))
425         time = 0;
426     if (!isfinite(duration))
427         duration = 0;
428     int seconds = static_cast<int>(fabsf(time));
429     int hours = seconds / (60 * 60);
430     int minutes = (seconds / 60) % 60;
431     seconds %= 60;
432
433     // duration defines the format of how the time is rendered
434     int durationSecs = static_cast<int>(fabsf(duration));
435     int durationHours = durationSecs / (60 * 60);
436     int durationMins = (durationSecs / 60) % 60;
437
438     if (durationHours || hours)
439         return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
440     if (durationMins > 9)
441         return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
442
443     return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
444 }
445
446 String RenderMediaControlsChromium::formatMediaControlsTime(float time)
447 {
448     return formatChromiumMediaControlsTime(time, time);
449 }
450
451 String RenderMediaControlsChromium::formatMediaControlsCurrentTime(float currentTime, float duration)
452 {
453     return formatChromiumMediaControlsTime(currentTime, duration);
454 }
455
456 String RenderMediaControlsChromium::formatMediaControlsRemainingTime(float currentTime, float duration)
457 {
458     return formatChromiumMediaControlsTime(currentTime - duration, duration);
459 }
460
461 #endif  // #if ENABLE(VIDEO)
462
463 } // namespace WebCore