Change the volume slider to horizontal rendering for Chrome video controls.
[WebKit-https.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* soundFull = platformResource("mediaSoundFull");
78     static Image* soundNone = platformResource("mediaSoundNone");
79     static Image* soundDisabled = platformResource("mediaSoundDisabled");
80
81     if (!hasSource(mediaElement) || !mediaElement->hasAudio())
82         return paintMediaButton(paintInfo.context, rect, soundDisabled);
83
84     return paintMediaButton(paintInfo.context, rect, mediaElement->muted() ? soundNone: soundFull);
85 }
86
87 static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
88 {
89     HTMLMediaElement* mediaElement = toParentMediaElement(object);
90     if (!mediaElement)
91         return false;
92
93     static Image* mediaPlay = platformResource("mediaPlay");
94     static Image* mediaPause = platformResource("mediaPause");
95     static Image* mediaPlayDisabled = platformResource("mediaPlayDisabled");
96
97     if (!hasSource(mediaElement))
98         return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled);
99
100     return paintMediaButton(paintInfo.context, rect, mediaElement->canPlay() ? mediaPlay : mediaPause);
101 }
102
103 static Image* getMediaSliderThumb()
104 {
105     static Image* mediaSliderThumb = platformResource("mediaSliderThumb");
106     return mediaSliderThumb;
107 }
108
109 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
110 {
111     HTMLMediaElement* mediaElement = toParentMediaElement(object);
112     if (!mediaElement)
113         return false;
114
115     RenderStyle* style = object->style();
116     GraphicsContext* context = paintInfo.context;
117
118     // Draw the border of the time bar.
119     // FIXME: this should be a rounded rect but need to fix GraphicsContextSkia first.
120     // https://bugs.webkit.org/show_bug.cgi?id=30143
121     context->save();
122     context->setShouldAntialias(true);
123     context->setStrokeStyle(SolidStroke);
124     context->setStrokeColor(style->visitedDependentColor(CSSPropertyBorderLeftColor), ColorSpaceDeviceRGB);
125     context->setStrokeThickness(style->borderLeftWidth());
126     context->setFillColor(style->visitedDependentColor(CSSPropertyBackgroundColor), ColorSpaceDeviceRGB);
127     context->drawRect(rect);
128     context->restore();
129
130     // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be
131     // distracting/'busy' to show all of them, show only the buffered range containing the current play head.
132     IntRect bufferedRect = rect;
133     bufferedRect.inflate(-style->borderLeftWidth());
134
135     RefPtr<TimeRanges> bufferedTimeRanges = mediaElement->buffered();
136     float duration = mediaElement->duration();
137     float currentTime = mediaElement->currentTime();
138     if (isnan(duration) || isinf(duration) || !duration || isnan(currentTime))
139         return true;
140
141     for (unsigned i = 0; i < bufferedTimeRanges->length(); ++i) {
142         float start = bufferedTimeRanges->start(i, ASSERT_NO_EXCEPTION);
143         float end = bufferedTimeRanges->end(i, ASSERT_NO_EXCEPTION);
144         if (isnan(start) || isnan(end) || start > currentTime || end < currentTime)
145             continue;
146         float startFraction = start / duration;
147         float endFraction = end / duration;
148         float widthFraction = endFraction - startFraction;
149         bufferedRect.move(startFraction * bufferedRect.width(), 0);
150         bufferedRect.setWidth(widthFraction * bufferedRect.width());
151
152         // Don't bother drawing an empty area.
153         if (bufferedRect.isEmpty())
154             return true;
155
156         IntPoint sliderTopLeft = bufferedRect.location();
157         IntPoint sliderBottomLeft = sliderTopLeft;
158         sliderBottomLeft.move(0, bufferedRect.height());
159
160         RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft);
161         Color startColor = object->style()->visitedDependentColor(CSSPropertyColor);
162         gradient->addColorStop(0.0, startColor);
163         gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha()));
164
165         context->save();
166         context->setStrokeStyle(NoStroke);
167         context->setFillGradient(gradient);
168         context->fillRect(bufferedRect);
169         context->restore();
170         return true;
171     }
172
173     return true;
174 }
175
176 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
177 {
178     ASSERT(object->node());
179     Node* hostNode = object->node()->shadowAncestorNode();
180     ASSERT(hostNode);
181     HTMLMediaElement* mediaElement = toParentMediaElement(hostNode);
182     if (!mediaElement)
183         return false;
184
185     if (!hasSource(mediaElement))
186         return true;
187
188     Image* mediaSliderThumb = getMediaSliderThumb();
189     return paintMediaButton(paintInfo.context, rect, mediaSliderThumb);
190 }
191
192 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
193 {
194     HTMLMediaElement* mediaElement = toParentMediaElement(object);
195     if (!mediaElement)
196         return false;
197
198     GraphicsContext* context = paintInfo.context;
199     Color originalColor = context->strokeColor();
200     if (originalColor != Color::white)
201         context->setStrokeColor(Color::white, ColorSpaceDeviceRGB);
202
203     int y = rect.y() + rect.height() / 2;
204     context->drawLine(IntPoint(rect.x(), y),  IntPoint(rect.x() + rect.width(), y));
205
206     if (originalColor != Color::white)
207         context->setStrokeColor(originalColor, ColorSpaceDeviceRGB);
208     return true;
209 }
210
211 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
212 {
213     static Image* mediaVolumeSliderThumb = platformResource("mediaVolumeSliderThumb");
214     return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb);
215 }
216
217 static bool paintMediaTimelineContainer(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
218 {
219     HTMLMediaElement* mediaElement = toParentMediaElement(object);
220     if (!mediaElement)
221         return false;
222
223     if (!rect.isEmpty()) {
224         GraphicsContext* context = paintInfo.context;
225         Color originalColor = context->strokeColor();
226         float originalThickness = context->strokeThickness();
227         StrokeStyle originalStyle = context->strokeStyle();
228
229         context->setStrokeStyle(SolidStroke);
230
231         // Draw the left border using CSS defined width and color.
232         context->setStrokeThickness(object->style()->borderLeftWidth());
233         context->setStrokeColor(object->style()->visitedDependentColor(CSSPropertyBorderLeftColor).rgb(), ColorSpaceDeviceRGB);
234         context->drawLine(IntPoint(rect.x() + 1, rect.y()),
235                           IntPoint(rect.x() + 1, rect.y() + rect.height()));
236
237         // Draw the right border using CSS defined width and color.
238         context->setStrokeThickness(object->style()->borderRightWidth());
239         context->setStrokeColor(object->style()->visitedDependentColor(CSSPropertyBorderRightColor).rgb(), ColorSpaceDeviceRGB);
240         context->drawLine(IntPoint(rect.x() + rect.width() - 1, rect.y()),
241                           IntPoint(rect.x() + rect.width() - 1, rect.y() + rect.height()));
242
243         context->setStrokeColor(originalColor, ColorSpaceDeviceRGB);
244         context->setStrokeThickness(originalThickness);
245         context->setStrokeStyle(originalStyle);
246     }
247     return true;
248 }
249
250 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
251 {
252     HTMLMediaElement* mediaElement = toParentMediaElement(object);
253     if (!mediaElement)
254         return false;
255
256     DEFINE_STATIC_LOCAL(Image*, mediaFullscreen, (platformResource("mediaFullscreen")));
257     return paintMediaButton(paintInfo.context, rect, mediaFullscreen);
258 }
259
260 bool RenderMediaControlsChromium::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
261 {
262     switch (part) {
263     case MediaMuteButton:
264     case MediaUnMuteButton:
265         return paintMediaMuteButton(object, paintInfo, rect);
266     case MediaPauseButton:
267     case MediaPlayButton:
268         return paintMediaPlayButton(object, paintInfo, rect);
269     case MediaSlider:
270         return paintMediaSlider(object, paintInfo, rect);
271     case MediaSliderThumb:
272         return paintMediaSliderThumb(object, paintInfo, rect);
273     case MediaVolumeSlider:
274         return paintMediaVolumeSlider(object, paintInfo, rect);
275     case MediaVolumeSliderThumb:
276         return paintMediaVolumeSliderThumb(object, paintInfo, rect);
277     case MediaTimelineContainer:
278         return paintMediaTimelineContainer(object, paintInfo, rect);
279     case MediaEnterFullscreenButton:
280     case MediaExitFullscreenButton:
281         return paintMediaFullscreenButton(object, paintInfo, rect);
282     case MediaVolumeSliderMuteButton:
283     case MediaSeekBackButton:
284     case MediaSeekForwardButton:
285     case MediaVolumeSliderContainer:
286     case MediaCurrentTimeDisplay:
287     case MediaTimeRemainingDisplay:
288     case MediaControlsPanel:
289     case MediaRewindButton:
290     case MediaReturnToRealtimeButton:
291     case MediaStatusDisplay:
292     case MediaShowClosedCaptionsButton:
293     case MediaHideClosedCaptionsButton:
294     case MediaTextTrackDisplayContainer:
295     case MediaTextTrackDisplay:
296     case MediaFullScreenVolumeSlider:
297     case MediaFullScreenVolumeSliderThumb:
298         ASSERT_NOT_REACHED();
299         break;
300     }
301     return false;
302 }
303
304 void RenderMediaControlsChromium::adjustMediaSliderThumbSize(RenderStyle* style)
305 {
306     static Image* mediaSliderThumb = platformResource("mediaSliderThumb");
307     static Image* mediaVolumeSliderThumb = platformResource("mediaVolumeSliderThumb");
308
309     Image* thumbImage = 0;
310     if (style->appearance() == MediaSliderThumbPart)
311         thumbImage = mediaSliderThumb;
312     else if (style->appearance() == MediaVolumeSliderThumbPart)
313         thumbImage = mediaVolumeSliderThumb;
314
315     float zoomLevel = style->effectiveZoom();
316     if (thumbImage) {
317         style->setWidth(Length(static_cast<int>(thumbImage->width() * zoomLevel), Fixed));
318         style->setHeight(Length(static_cast<int>(thumbImage->height() * zoomLevel), Fixed));
319     }
320 }
321
322 #endif  // #if ENABLE(VIDEO)
323
324 } // namespace WebCore