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