1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO)
29
30#include "FullscreenVideoController.h"
31
32#include "WebKitDLL.h"
33#include "WebView.h"
34#include <ApplicationServices/ApplicationServices.h>
35#include <WebCore/BitmapInfo.h>
36#include <WebCore/Chrome.h>
37#include <WebCore/Font.h>
38#include <WebCore/FontSelector.h>
39#include <WebCore/GraphicsContext.h>
40#include <WebCore/HWndDC.h>
41#include <WebCore/Page.h>
42#include <WebCore/PlatformCALayer.h>
43#include <WebCore/TextRun.h>
44#include <WebKitSystemInterface/WebKitSystemInterface.h>
45#include <windowsx.h>
46#include <wtf/OwnPtr.h>
47#include <wtf/PassOwnPtr.h>
48#include <wtf/StdLibExtras.h>
49
50using namespace std;
51using namespace WebCore;
52
53static const float timerInterval = 0.033;
54
55// HUD Size
56static const int windowHeight = 59;
57static const int windowWidth = 438;
58
59// Margins and button sizes
60static const int margin = 9;
61static const int marginTop = 9;
62static const int buttonSize = 25;
63static const int buttonMiniSize = 16;
64static const int volumeSliderWidth = 50;
65static const int timeSliderWidth = 310;
66static const int sliderHeight = 8;
67static const int volumeSliderButtonSize = 10;
68static const int timeSliderButtonSize = 8;
69static const int textSize = 11;
70static const float initialHUDPositionY = 0.9; // Initial Y position of HUD in percentage from top of screen
71
72// Background values
73static const int borderRadius = 12;
74static const int borderThickness = 2;
75
76// Colors
77static const unsigned int backgroundColor = 0xA0202020;
78static const unsigned int borderColor = 0xFFA0A0A0;
79static const unsigned int sliderGutterColor = 0xFF141414;
80static const unsigned int sliderButtonColor = 0xFF808080;
81static const unsigned int textColor = 0xFFFFFFFF;
82
83HUDButton::HUDButton(HUDButtonType type, const IntPoint& position)
84    : HUDWidget(IntRect(position, IntSize()))
85    , m_type(type)
86    , m_showAltButton(false)
87{
88    const char* buttonResource = 0;
89    const char* buttonResourceAlt = 0;
90    switch (m_type) {
91    case PlayPauseButton:
92        buttonResource = "fsVideoPlay";
93        buttonResourceAlt = "fsVideoPause";
94        break;
95    case TimeSliderButton:
96        break;
97    case VolumeUpButton:
98        buttonResource = "fsVideoAudioVolumeHigh";
99        break;
100    case VolumeSliderButton:
101        break;
102    case VolumeDownButton:
103        buttonResource = "fsVideoAudioVolumeLow";
104        break;
105    case ExitFullscreenButton:
106        buttonResource = "fsVideoExitFullscreen";
107        break;
108    }
109
110    if (buttonResource) {
111        m_buttonImage = Image::loadPlatformResource(buttonResource);
112        m_rect.setWidth(m_buttonImage->width());
113        m_rect.setHeight(m_buttonImage->height());
114    }
115    if (buttonResourceAlt)
116        m_buttonImageAlt = Image::loadPlatformResource(buttonResourceAlt);
117}
118
119void HUDButton::draw(GraphicsContext& context)
120{
121    Image* image = (m_showAltButton && m_buttonImageAlt) ? m_buttonImageAlt.get() : m_buttonImage.get();
122    context.drawImage(image, ColorSpaceDeviceRGB, m_rect.location());
123}
124
125HUDSlider::HUDSlider(HUDSliderButtonShape shape, int buttonSize, const IntRect& rect)
126    : HUDWidget(rect)
127    , m_buttonShape(shape)
128    , m_buttonSize(buttonSize)
129    , m_buttonPosition(0)
130    , m_dragStartOffset(0)
131{
132}
133
134void HUDSlider::draw(GraphicsContext& context)
135{
136    // Draw gutter
137    IntSize radius(m_rect.height() / 2, m_rect.height() / 2);
138    context.fillRoundedRect(m_rect, radius, radius, radius, radius, Color(sliderGutterColor), ColorSpaceDeviceRGB);
139
140    // Draw button
141    context.setStrokeColor(Color(sliderButtonColor), ColorSpaceDeviceRGB);
142    context.setFillColor(Color(sliderButtonColor), ColorSpaceDeviceRGB);
143
144    if (m_buttonShape == RoundButton) {
145        context.drawEllipse(IntRect(m_rect.location().x() + m_buttonPosition, m_rect.location().y() - (m_buttonSize - m_rect.height()) / 2, m_buttonSize, m_buttonSize));
146        return;
147    }
148
149    // Draw a diamond
150    FloatPoint points[4];
151    float half = static_cast<float>(m_buttonSize) / 2;
152    points[0].setX(m_rect.location().x() + m_buttonPosition + half);
153    points[0].setY(m_rect.location().y());
154    points[1].setX(m_rect.location().x() + m_buttonPosition + m_buttonSize);
155    points[1].setY(m_rect.location().y() + half);
156    points[2].setX(m_rect.location().x() + m_buttonPosition + half);
157    points[2].setY(m_rect.location().y() + m_buttonSize);
158    points[3].setX(m_rect.location().x() + m_buttonPosition);
159    points[3].setY(m_rect.location().y() + half);
160    context.drawConvexPolygon(4, points, true);
161}
162
163void HUDSlider::drag(const IntPoint& point, bool start)
164{
165    if (start) {
166        // When we start, we need to snap the slider position to the x position if we clicked the gutter.
167        // But if we click the button, we need to drag relative to where we clicked down. We only need
168        // to check X because we would not even get here unless Y were already inside.
169        int relativeX = point.x() - m_rect.location().x();
170        if (relativeX >= m_buttonPosition && relativeX <= m_buttonPosition + m_buttonSize)
171            m_dragStartOffset = point.x() - m_buttonPosition;
172        else
173            m_dragStartOffset = m_rect.location().x() + m_buttonSize / 2;
174    }
175
176    m_buttonPosition = max(0, min(m_rect.width() - m_buttonSize, point.x() - m_dragStartOffset));
177}
178
179#if USE(ACCELERATED_COMPOSITING)
180class FullscreenVideoController::LayerClient : public WebCore::PlatformCALayerClient {
181public:
182    LayerClient(FullscreenVideoController* parent) : m_parent(parent) { }
183
184private:
185    virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*);
186    virtual bool platformCALayerRespondsToLayoutChanges() const { return true; }
187
188    virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { }
189    virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; }
190    virtual void platformCALayerPaintContents(GraphicsContext&, const IntRect& inClip) { }
191    virtual bool platformCALayerShowDebugBorders() const { return false; }
192    virtual bool platformCALayerShowRepaintCounter(PlatformCALayer*) const { return false; }
193    virtual int platformCALayerIncrementRepaintCount() { return 0; }
194
195    virtual bool platformCALayerContentsOpaque() const { return false; }
196    virtual bool platformCALayerDrawsContent() const { return false; }
197    virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { }
198    virtual void platformCALayerDidCreateTiles(const Vector<FloatRect>&) { }
199    virtual float platformCALayerDeviceScaleFactor() { return 1; }
200
201    FullscreenVideoController* m_parent;
202};
203
204void FullscreenVideoController::LayerClient::platformCALayerLayoutSublayersOfLayer(PlatformCALayer* layer)
205{
206    ASSERT_ARG(layer, layer == m_parent->m_rootChild);
207
208    HTMLMediaElement* mediaElement = m_parent->m_mediaElement.get();
209    if (!mediaElement)
210        return;
211
212
213    PlatformCALayer* videoLayer = PlatformCALayer::platformCALayer(mediaElement->platformLayer());
214    if (!videoLayer || videoLayer->superlayer() != layer)
215        return;
216
217    FloatRect layerBounds = layer->bounds();
218
219    FloatSize videoSize = mediaElement->player()->naturalSize();
220    float scaleFactor;
221    if (videoSize.aspectRatio() > layerBounds.size().aspectRatio())
222        scaleFactor = layerBounds.width() / videoSize.width();
223    else
224        scaleFactor = layerBounds.height() / videoSize.height();
225    videoSize.scale(scaleFactor);
226
227    // Calculate the centered position based on the videoBounds and layerBounds:
228    FloatPoint videoPosition;
229    FloatPoint videoOrigin;
230    videoOrigin.setX((layerBounds.width() - videoSize.width()) * 0.5);
231    videoOrigin.setY((layerBounds.height() - videoSize.height()) * 0.5);
232    videoLayer->setFrame(FloatRect(videoOrigin, videoSize));
233}
234#endif
235
236FullscreenVideoController::FullscreenVideoController()
237    : m_hudWindow(0)
238    , m_playPauseButton(HUDButton::PlayPauseButton, IntPoint((windowWidth - buttonSize) / 2, marginTop))
239    , m_timeSliderButton(HUDButton::TimeSliderButton, IntPoint(0, 0))
240    , m_volumeUpButton(HUDButton::VolumeUpButton, IntPoint(margin + buttonMiniSize + volumeSliderWidth + buttonMiniSize / 2, marginTop + (buttonSize - buttonMiniSize) / 2))
241    , m_volumeSliderButton(HUDButton::VolumeSliderButton, IntPoint(0, 0))
242    , m_volumeDownButton(HUDButton::VolumeDownButton, IntPoint(margin, marginTop + (buttonSize - buttonMiniSize) / 2))
243    , m_exitFullscreenButton(HUDButton::ExitFullscreenButton, IntPoint(windowWidth - 2 * margin - buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2))
244    , m_volumeSlider(HUDSlider::RoundButton, volumeSliderButtonSize, IntRect(IntPoint(margin + buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2 + buttonMiniSize / 2 - sliderHeight / 2), IntSize(volumeSliderWidth, sliderHeight)))
245    , m_timeSlider(HUDSlider::DiamondButton, timeSliderButtonSize, IntRect(IntPoint(windowWidth / 2 - timeSliderWidth / 2, windowHeight - margin - sliderHeight), IntSize(timeSliderWidth, sliderHeight)))
246    , m_hitWidget(0)
247    , m_movingWindow(false)
248    , m_timer(this, &FullscreenVideoController::timerFired)
249#if USE(ACCELERATED_COMPOSITING)
250    , m_layerClient(adoptPtr(new LayerClient(this)))
251    , m_rootChild(PlatformCALayer::create(PlatformCALayer::LayerTypeLayer, m_layerClient.get()))
252#endif
253    , m_fullscreenWindow(adoptPtr(new MediaPlayerPrivateFullscreenWindow(this)))
254{
255}
256
257FullscreenVideoController::~FullscreenVideoController()
258{
259#if USE(ACCELERATED_COMPOSITING)
260    m_rootChild->setOwner(0);
261#endif
262}
263
264void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement)
265{
266    if (mediaElement == m_mediaElement)
267        return;
268
269    m_mediaElement = mediaElement;
270    if (!m_mediaElement) {
271        // Can't do full-screen, just get out
272        exitFullscreen();
273    }
274}
275
276void FullscreenVideoController::enterFullscreen()
277{
278    if (!m_mediaElement)
279        return;
280
281    WebView* webView = kit(m_mediaElement->document()->page());
282    HWND parentHwnd = webView ? webView->viewWindow() : 0;
283
284    m_fullscreenWindow->createWindow(parentHwnd);
285    ::ShowWindow(m_fullscreenWindow->hwnd(), SW_SHOW);
286#if USE(ACCELERATED_COMPOSITING)
287    m_fullscreenWindow->setRootChildLayer(m_rootChild);
288
289    PlatformCALayer* videoLayer = PlatformCALayer::platformCALayer(m_mediaElement->platformLayer());
290    m_rootChild->appendSublayer(videoLayer);
291    m_rootChild->setNeedsLayout();
292    m_rootChild->setGeometryFlipped(1);
293#endif
294
295    RECT windowRect;
296    GetClientRect(m_fullscreenWindow->hwnd(), &windowRect);
297    m_fullscreenSize.setWidth(windowRect.right - windowRect.left);
298    m_fullscreenSize.setHeight(windowRect.bottom - windowRect.top);
299
300    createHUDWindow();
301}
302
303void FullscreenVideoController::exitFullscreen()
304{
305    SetWindowLongPtr(m_hudWindow, 0, 0);
306
307    if (m_fullscreenWindow)
308        m_fullscreenWindow = nullptr;
309
310    ASSERT(!IsWindow(m_hudWindow));
311    m_hudWindow = 0;
312
313    // We previously ripped the mediaElement's platform layer out
314    // of its orginial layer tree to display it in our fullscreen
315    // window.  Now, we need to get the layer back in its original
316    // tree.
317    //
318    // As a side effect of setting the player to invisible/visible,
319    // the player's layer will be recreated, and will be picked up
320    // the next time the layer tree is synched.
321    m_mediaElement->player()->setVisible(0);
322    m_mediaElement->player()->setVisible(1);
323}
324
325bool FullscreenVideoController::canPlay() const
326{
327    return m_mediaElement && m_mediaElement->canPlay();
328}
329
330void FullscreenVideoController::play()
331{
332    if (m_mediaElement)
333        m_mediaElement->play();
334}
335
336void FullscreenVideoController::pause()
337{
338    if (m_mediaElement)
339        m_mediaElement->pause();
340}
341
342float FullscreenVideoController::volume() const
343{
344    return m_mediaElement ? m_mediaElement->volume() : 0;
345}
346
347void FullscreenVideoController::setVolume(float volume)
348{
349    if (m_mediaElement) {
350        ExceptionCode ec;
351        m_mediaElement->setVolume(volume, ec);
352    }
353}
354
355float FullscreenVideoController::currentTime() const
356{
357    return m_mediaElement ? m_mediaElement->currentTime() : 0;
358}
359
360void FullscreenVideoController::setCurrentTime(float value)
361{
362    if (m_mediaElement) {
363        ExceptionCode ec;
364        m_mediaElement->setCurrentTime(value, ec);
365    }
366}
367
368float FullscreenVideoController::duration() const
369{
370    return m_mediaElement ? m_mediaElement->duration() : 0;
371}
372
373void FullscreenVideoController::beginScrubbing()
374{
375    if (m_mediaElement)
376        m_mediaElement->beginScrubbing();
377}
378
379void FullscreenVideoController::endScrubbing()
380{
381    if (m_mediaElement)
382        m_mediaElement->endScrubbing();
383}
384
385LRESULT FullscreenVideoController::fullscreenClientWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
386{
387    switch (message) {
388    case WM_CHAR:
389        onChar(wParam);
390        break;
391    case WM_KEYDOWN:
392        onKeyDown(wParam);
393        break;
394    case WM_LBUTTONDOWN:
395        onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
396        break;
397    case WM_MOUSEMOVE:
398        onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
399        break;
400    case WM_LBUTTONUP:
401        onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
402        break;
403    }
404
405    return DefWindowProc(wnd, message, wParam, lParam);
406}
407
408static const LPCWSTR fullscreenVideeoHUDWindowClassName = L"fullscreenVideeoHUDWindowClass";
409
410void FullscreenVideoController::registerHUDWindowClass()
411{
412    static bool haveRegisteredHUDWindowClass;
413    if (haveRegisteredHUDWindowClass)
414        return;
415
416    haveRegisteredHUDWindowClass = true;
417
418    WNDCLASSEX wcex;
419
420    wcex.cbSize = sizeof(WNDCLASSEX);
421
422    wcex.style = CS_HREDRAW | CS_VREDRAW;
423    wcex.lpfnWndProc = hudWndProc;
424    wcex.cbClsExtra = 0;
425    wcex.cbWndExtra = sizeof(FullscreenVideoController*);
426    wcex.hInstance = gInstance;
427    wcex.hIcon = 0;
428    wcex.hCursor = LoadCursor(0, IDC_ARROW);
429    wcex.hbrBackground = 0;
430    wcex.lpszMenuName = 0;
431    wcex.lpszClassName = fullscreenVideeoHUDWindowClassName;
432    wcex.hIconSm = 0;
433
434    RegisterClassEx(&wcex);
435}
436
437void FullscreenVideoController::createHUDWindow()
438{
439    m_hudPosition.setX((m_fullscreenSize.width() - windowWidth) / 2);
440    m_hudPosition.setY(m_fullscreenSize.height() * initialHUDPositionY - windowHeight / 2);
441
442    // Local variable that will hold the returned pixels. No need to cleanup this value. It
443    // will get cleaned up when m_bitmap is destroyed in the dtor
444    void* pixels;
445    BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(IntSize(windowWidth, windowHeight));
446    m_bitmap = adoptPtr(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0));
447
448    // Dirty the window so the HUD draws
449    RECT clearRect = { m_hudPosition.x(), m_hudPosition.y(), m_hudPosition.x() + windowWidth, m_hudPosition.y() + windowHeight };
450    InvalidateRect(m_fullscreenWindow->hwnd(), &clearRect, true);
451
452    m_playPauseButton.setShowAltButton(!canPlay());
453    m_volumeSlider.setValue(volume());
454    m_timeSlider.setValue(currentTime() / duration());
455
456    if (!canPlay())
457        m_timer.startRepeating(timerInterval);
458
459    registerHUDWindowClass();
460
461    m_hudWindow = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
462        fullscreenVideeoHUDWindowClassName, 0, WS_POPUP | WS_VISIBLE,
463        m_hudPosition.x(), m_hudPosition.y(), 0, 0, m_fullscreenWindow->hwnd(), 0, gInstance, 0);
464    ASSERT(::IsWindow(m_hudWindow));
465    SetWindowLongPtr(m_hudWindow, 0, reinterpret_cast<LONG_PTR>(this));
466
467    draw();
468}
469
470static String timeToString(float time)
471{
472    if (!std::isfinite(time))
473        time = 0;
474    int seconds = fabsf(time);
475    int hours = seconds / (60 * 60);
476    int minutes = (seconds / 60) % 60;
477    seconds %= 60;
478
479    if (hours) {
480        if (hours > 9)
481            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
482        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
483    }
484
485    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
486}
487
488void FullscreenVideoController::draw()
489{
490    OwnPtr<HDC> bitmapDC = adoptPtr(CreateCompatibleDC(HWndDC(m_hudWindow)));
491    HGDIOBJ oldBitmap = SelectObject(bitmapDC.get(), m_bitmap.get());
492
493    GraphicsContext context(bitmapDC.get(), true);
494
495    context.save();
496
497    // Draw the background
498    IntSize outerRadius(borderRadius, borderRadius);
499    IntRect outerRect(0, 0, windowWidth, windowHeight);
500    IntSize innerRadius(borderRadius - borderThickness, borderRadius - borderThickness);
501    IntRect innerRect(borderThickness, borderThickness, windowWidth - borderThickness * 2, windowHeight - borderThickness * 2);
502
503    context.fillRoundedRect(outerRect, outerRadius, outerRadius, outerRadius, outerRadius, Color(borderColor), ColorSpaceDeviceRGB);
504    context.setCompositeOperation(CompositeCopy);
505    context.fillRoundedRect(innerRect, innerRadius, innerRadius, innerRadius, innerRadius, Color(backgroundColor), ColorSpaceDeviceRGB);
506
507    // Draw the widgets
508    m_playPauseButton.draw(context);
509    m_volumeUpButton.draw(context);
510    m_volumeSliderButton.draw(context);
511    m_volumeDownButton.draw(context);
512    m_timeSliderButton.draw(context);
513    m_exitFullscreenButton.draw(context);
514    m_volumeSlider.draw(context);
515    m_timeSlider.draw(context);
516
517    // Draw the text strings
518    FontDescription desc;
519
520    NONCLIENTMETRICS metrics;
521    metrics.cbSize = sizeof(metrics);
522    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
523    desc.setOneFamily(metrics.lfSmCaptionFont.lfFaceName);
524
525    desc.setComputedSize(textSize);
526    Font font = Font(desc, 0, 0);
527    font.update(0);
528
529    String s;
530
531    // The y positioning of these two text strings is tricky because they are so small. They
532    // are currently positioned relative to the center of the slider and then down the font
533    // height / 4 (which is actually half of font height /2), which positions the center of
534    // the text at the center of the slider.
535    // Left string
536    s = timeToString(currentTime());
537    int fontHeight = font.fontMetrics().height();
538    TextRun leftText(s);
539    context.setFillColor(Color(textColor), ColorSpaceDeviceRGB);
540    context.drawText(font, leftText, IntPoint(windowWidth / 2 - timeSliderWidth / 2 - margin - font.width(leftText), windowHeight - margin - sliderHeight / 2 + fontHeight / 4));
541
542    // Right string
543    s = timeToString(currentTime() - duration());
544    TextRun rightText(s);
545    context.setFillColor(Color(textColor), ColorSpaceDeviceRGB);
546    context.drawText(font, rightText, IntPoint(windowWidth / 2 + timeSliderWidth / 2 + margin, windowHeight - margin - sliderHeight / 2 + fontHeight / 4));
547
548    // Copy to the window
549    BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
550    SIZE size = { windowWidth, windowHeight };
551    POINT sourcePoint = {0, 0};
552    POINT destPoint = { m_hudPosition.x(), m_hudPosition.y() };
553    BOOL result = UpdateLayeredWindow(m_hudWindow, 0, &destPoint, &size, bitmapDC.get(), &sourcePoint, 0, &blendFunction, ULW_ALPHA);
554
555    context.restore();
556
557    ::SelectObject(bitmapDC.get(), oldBitmap);
558}
559
560LRESULT FullscreenVideoController::hudWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
561{
562    LONG_PTR longPtr = GetWindowLongPtr(wnd, 0);
563    FullscreenVideoController* controller = reinterpret_cast<FullscreenVideoController*>(longPtr);
564    if (!controller)
565        return DefWindowProc(wnd, message, wParam, lParam);
566
567    switch (message) {
568    case WM_CHAR:
569        controller->onChar(wParam);
570        break;
571    case WM_KEYDOWN:
572        controller->onKeyDown(wParam);
573        break;
574    case WM_LBUTTONDOWN:
575        controller->onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
576        break;
577    case WM_MOUSEMOVE:
578        controller->onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
579        break;
580    case WM_LBUTTONUP:
581        controller->onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
582        break;
583    }
584
585    return DefWindowProc(wnd, message, wParam, lParam);
586}
587
588void FullscreenVideoController::onChar(int c)
589{
590    if (c == VK_ESCAPE) {
591        if (m_mediaElement)
592            m_mediaElement->exitFullscreen();
593    } else if (c == VK_SPACE)
594        togglePlay();
595}
596
597void FullscreenVideoController::onKeyDown(int virtualKey)
598{
599    if (virtualKey == VK_ESCAPE) {
600        if (m_mediaElement)
601            m_mediaElement->exitFullscreen();
602    }
603}
604
605void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*)
606{
607    // Update the time slider
608    m_timeSlider.setValue(currentTime() / duration());
609    draw();
610}
611
612void FullscreenVideoController::onMouseDown(const IntPoint& point)
613{
614    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
615
616    // Don't bother hit testing if we're outside the bounds of the window
617    if (convertedPoint.x() < 0 || convertedPoint.x() >= windowWidth || convertedPoint.y() < 0 || convertedPoint.y() >= windowHeight)
618        return;
619
620    m_hitWidget = 0;
621    m_movingWindow = false;
622
623    if (m_playPauseButton.hitTest(convertedPoint))
624        m_hitWidget = &m_playPauseButton;
625    else if (m_exitFullscreenButton.hitTest(convertedPoint))
626        m_hitWidget = &m_exitFullscreenButton;
627    else if (m_volumeUpButton.hitTest(convertedPoint))
628        m_hitWidget = &m_volumeUpButton;
629    else if (m_volumeDownButton.hitTest(convertedPoint))
630        m_hitWidget = &m_volumeDownButton;
631    else if (m_volumeSlider.hitTest(convertedPoint)) {
632        m_hitWidget = &m_volumeSlider;
633        m_volumeSlider.drag(convertedPoint, true);
634        setVolume(m_volumeSlider.value());
635    } else if (m_timeSlider.hitTest(convertedPoint)) {
636        m_hitWidget = &m_timeSlider;
637        m_timeSlider.drag(convertedPoint, true);
638        beginScrubbing();
639        setCurrentTime(m_timeSlider.value() * duration());
640    }
641
642    // If we did not pick any of our widgets we are starting a window move
643    if (!m_hitWidget) {
644        m_moveOffset = convertedPoint;
645        m_movingWindow = true;
646    }
647
648    draw();
649}
650
651void FullscreenVideoController::onMouseMove(const IntPoint& point)
652{
653    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
654
655    if (m_hitWidget) {
656        m_hitWidget->drag(convertedPoint, false);
657        if (m_hitWidget == &m_volumeSlider)
658            setVolume(m_volumeSlider.value());
659        else if (m_hitWidget == &m_timeSlider)
660            setCurrentTime(m_timeSlider.value() * duration());
661        draw();
662    } else if (m_movingWindow)
663        m_hudPosition.move(convertedPoint.x() - m_moveOffset.x(), convertedPoint.y() - m_moveOffset.y());
664}
665
666void FullscreenVideoController::onMouseUp(const IntPoint& point)
667{
668    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
669    m_movingWindow = false;
670
671    if (m_hitWidget) {
672        if (m_hitWidget == &m_playPauseButton && m_playPauseButton.hitTest(convertedPoint))
673            togglePlay();
674        else if (m_hitWidget == &m_volumeUpButton && m_volumeUpButton.hitTest(convertedPoint)) {
675            setVolume(1);
676            m_volumeSlider.setValue(1);
677        } else if (m_hitWidget == &m_volumeDownButton && m_volumeDownButton.hitTest(convertedPoint)) {
678            setVolume(0);
679            m_volumeSlider.setValue(0);
680        } else if (m_hitWidget == &m_timeSlider)
681            endScrubbing();
682        else if (m_hitWidget == &m_exitFullscreenButton && m_exitFullscreenButton.hitTest(convertedPoint)) {
683            m_hitWidget = 0;
684            if (m_mediaElement)
685                m_mediaElement->exitFullscreen();
686            return;
687        }
688    }
689
690    m_hitWidget = 0;
691    draw();
692}
693
694void FullscreenVideoController::togglePlay()
695{
696    if (canPlay())
697        play();
698    else
699        pause();
700
701    m_playPauseButton.setShowAltButton(!canPlay());
702
703    // Run a timer while the video is playing so we can keep the time
704    // slider and time values up to date.
705    if (!canPlay())
706        m_timer.startRepeating(timerInterval);
707    else
708        m_timer.stop();
709
710    draw();
711}
712
713#endif
714