WebKit/win/FullscreenVideoController.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit/win/FullscreenVideoController.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+
+#if ENABLE(VIDEO)
+
+#include "FullscreenVideoController.h"
+
+#include "WebKitDLL.h"
+#include "WebView.h"
+#include <ApplicationServices/ApplicationServices.h>
+#include <WebCore/BitmapInfo.h>
+#include <WebCore/Chrome.h>
+#include <WebCore/Font.h>
+#include <WebCore/FontSelector.h>
+#include <WebCore/GraphicsContext.h>
+#include <WebCore/Page.h>
+#include <WebCore/TextRun.h>
+#include <WebCore/WKCACFLayer.h>
+#include <WebKitSystemInterface/WebKitSystemInterface.h>
+#include <windowsx.h>
+#include <wtf/StdLibExtras.h>
+
+using namespace std;
+using namespace WebCore;
+
+static const float timerInterval = 0.033;
+
+// HUD Size
+static const int windowHeight = 59;
+static const int windowWidth = 438;
+
+// Margins and button sizes
+static const int margin = 9;
+static const int marginTop = 9;
+static const int buttonSize = 25;
+static const int buttonMiniSize = 16;
+static const int volumeSliderWidth = 50;
+static const int timeSliderWidth = 310;
+static const int sliderHeight = 8;
+static const int volumeSliderButtonSize = 10;
+static const int timeSliderButtonSize = 8;
+static const int textSize = 11;
+static const float initialHUDPositionY = 0.9; // Initial Y position of HUD in percentage from top of screen
+
+// Background values
+static const int borderRadius = 12;
+static const int borderThickness = 2;
+
+// Colors
+static const unsigned int backgroundColor = 0xA0202020;
+static const unsigned int borderColor = 0xFFA0A0A0;
+static const unsigned int sliderGutterColor = 0xFF141414;
+static const unsigned int sliderButtonColor = 0xFF808080;
+static const unsigned int textColor = 0xFFFFFFFF;
+
+HUDButton::HUDButton(HUDButtonType type, const IntPoint& position)
+    : HUDWidget(IntRect(position, IntSize()))
+    , m_type(type)
+    , m_showAltButton(false)
+{
+    const char* buttonResource = 0;
+    const char* buttonResourceAlt = 0;
+    switch (m_type) {
+    case PlayPauseButton:
+        buttonResource = "fsVideoPlay";
+        buttonResourceAlt = "fsVideoPause";
+        break;
+    case TimeSliderButton:
+        break;
+    case VolumeUpButton:
+        buttonResource = "fsVideoAudioVolumeHigh";
+        break;
+    case VolumeSliderButton:
+        break;
+    case VolumeDownButton:
+        buttonResource = "fsVideoAudioVolumeLow";
+        break;
+    case ExitFullscreenButton:
+        buttonResource = "fsVideoExitFullscreen";
+        break;
+    }
+
+    if (buttonResource) {
+        m_buttonImage = Image::loadPlatformResource(buttonResource);
+        m_rect.setWidth(m_buttonImage->width());
+        m_rect.setHeight(m_buttonImage->height());
+    }
+    if (buttonResourceAlt)
+        m_buttonImageAlt = Image::loadPlatformResource(buttonResourceAlt);
+}
+
+void HUDButton::draw(GraphicsContext& context)
+{
+    Image* image = (m_showAltButton && m_buttonImageAlt) ? m_buttonImageAlt.get() : m_buttonImage.get();
+    context.drawImage(image, DeviceColorSpace, m_rect.location());
+}
+
+HUDSlider::HUDSlider(HUDSliderButtonShape shape, int buttonSize, const IntRect& rect)
+    : HUDWidget(rect)
+    , m_buttonShape(shape)
+    , m_buttonSize(buttonSize)
+    , m_buttonPosition(0)
+    , m_dragStartOffset(0)
+{
+}
+
+void HUDSlider::draw(GraphicsContext& context)
+{
+    // Draw gutter
+    IntSize radius(m_rect.height() / 2, m_rect.height() / 2);
+    context.fillRoundedRect(m_rect, radius, radius, radius, radius, Color(sliderGutterColor), DeviceColorSpace);
+
+    // Draw button
+    context.setStrokeColor(Color(sliderButtonColor), DeviceColorSpace);
+    context.setFillColor(Color(sliderButtonColor), DeviceColorSpace);
+
+    if (m_buttonShape == RoundButton) {
+        context.drawEllipse(IntRect(m_rect.location().x() + m_buttonPosition, m_rect.location().y() - (m_buttonSize - m_rect.height()) / 2, m_buttonSize, m_buttonSize));
+        return;
+    }
+
+    // Draw a diamond
+    FloatPoint points[4];
+    float half = static_cast<float>(m_buttonSize) / 2;
+    points[0].setX(m_rect.location().x() + m_buttonPosition + half);
+    points[0].setY(m_rect.location().y());
+    points[1].setX(m_rect.location().x() + m_buttonPosition + m_buttonSize);
+    points[1].setY(m_rect.location().y() + half);
+    points[2].setX(m_rect.location().x() + m_buttonPosition + half);
+    points[2].setY(m_rect.location().y() + m_buttonSize);
+    points[3].setX(m_rect.location().x() + m_buttonPosition);
+    points[3].setY(m_rect.location().y() + half);
+    context.drawConvexPolygon(4, points, true);
+}
+
+void HUDSlider::drag(const IntPoint& point, bool start)
+{
+    if (start) {
+        // When we start, we need to snap the slider position to the x position if we clicked the gutter.
+        // But if we click the button, we need to drag relative to where we clicked down. We only need
+        // to check X because we would not even get here unless Y were already inside.
+        int relativeX = point.x() - m_rect.location().x();
+        if (relativeX >= m_buttonPosition && relativeX <= m_buttonPosition + m_buttonSize)
+            m_dragStartOffset = point.x() - m_buttonPosition;
+        else
+            m_dragStartOffset = m_rect.location().x() + m_buttonSize / 2;
+    }
+
+    m_buttonPosition = max(0, min(m_rect.width() - m_buttonSize, point.x() - m_dragStartOffset));
+}
+
+#if USE(ACCELERATED_COMPOSITING)
+class FullscreenVideoController::LayoutClient : public WKCACFLayerLayoutClient {
+public:
+    LayoutClient(FullscreenVideoController* parent);
+    void layoutSublayersOfLayer(WKCACFLayer* layer);
+
+    FullscreenVideoController* m_parent;
+};
+
+FullscreenVideoController::LayoutClient::LayoutClient(FullscreenVideoController* parent)
+    : m_parent(parent)
+{
+}
+
+void FullscreenVideoController::LayoutClient::layoutSublayersOfLayer(WKCACFLayer* layer) 
+{
+    ASSERT_ARG(layer, layer == m_parent->m_rootChild);
+
+    HTMLMediaElement* mediaElement = m_parent->m_mediaElement.get();
+    if (!mediaElement)
+        return;
+
+    WKCACFLayer* videoLayer = mediaElement->platformLayer();
+    if (!videoLayer || videoLayer->superlayer() != layer)
+        return;
+
+    FloatRect layerBounds = layer->bounds();
+
+    FloatSize videoSize = mediaElement->player()->naturalSize();
+    float scaleFactor;
+    if (videoSize.aspectRatio() > layerBounds.size().aspectRatio())
+        scaleFactor = layerBounds.width() / videoSize.width();
+    else
+        scaleFactor = layerBounds.height() / videoSize.height();
+    videoSize.scale(scaleFactor);
+
+    // Calculate the centered position based on the videoBounds and layerBounds:
+    FloatPoint videoPosition;
+    FloatPoint videoOrigin;
+    videoOrigin.setX((layerBounds.width() - videoSize.width()) * 0.5);
+    videoOrigin.setY((layerBounds.height() - videoSize.height()) * 0.5);
+    videoLayer->setFrame(FloatRect(videoOrigin, videoSize));
+}
+#endif 
+
+FullscreenVideoController::FullscreenVideoController()
+    : m_hudWindow(0)
+    , m_playPauseButton(HUDButton::PlayPauseButton, IntPoint((windowWidth - buttonSize) / 2, marginTop))
+    , m_timeSliderButton(HUDButton::TimeSliderButton, IntPoint(0, 0))
+    , m_volumeUpButton(HUDButton::VolumeUpButton, IntPoint(margin + buttonMiniSize + volumeSliderWidth + buttonMiniSize / 2, marginTop + (buttonSize - buttonMiniSize) / 2))
+    , m_volumeSliderButton(HUDButton::VolumeSliderButton, IntPoint(0, 0))
+    , m_volumeDownButton(HUDButton::VolumeDownButton, IntPoint(margin, marginTop + (buttonSize - buttonMiniSize) / 2))
+    , m_exitFullscreenButton(HUDButton::ExitFullscreenButton, IntPoint(windowWidth - 2 * margin - buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2))
+    , m_volumeSlider(HUDSlider::RoundButton, volumeSliderButtonSize, IntRect(IntPoint(margin + buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2 + buttonMiniSize / 2 - sliderHeight / 2), IntSize(volumeSliderWidth, sliderHeight)))
+    , m_timeSlider(HUDSlider::DiamondButton, timeSliderButtonSize, IntRect(IntPoint(windowWidth / 2 - timeSliderWidth / 2, windowHeight - margin - sliderHeight), IntSize(timeSliderWidth, sliderHeight)))
+    , m_hitWidget(0)
+    , m_movingWindow(false)
+    , m_timer(this, &FullscreenVideoController::timerFired)
+#if USE(ACCELERATED_COMPOSITING)
+    , m_rootChild(WKCACFLayer::create(WKCACFLayer::Layer))
+    , m_layoutClient(new LayoutClient(this))
+#endif
+    , m_fullscreenWindow(new MediaPlayerPrivateFullscreenWindow(this))
+{
+#if USE(ACCELERATED_COMPOSITING)
+    m_rootChild->setLayoutClient(m_layoutClient.get());
+#endif
+}
+
+FullscreenVideoController::~FullscreenVideoController()
+{
+#if USE(ACCELERATED_COMPOSITING)
+    m_rootChild->setLayoutClient(0);
+#endif
+}
+
+void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement)
+{
+    if (mediaElement == m_mediaElement)
+        return;
+
+    m_mediaElement = mediaElement;
+    if (!m_mediaElement) {
+        // Can't do full-screen, just get out
+        exitFullscreen();
+    }
+}
+
+void FullscreenVideoController::enterFullscreen()
+{
+    if (!m_mediaElement)
+        return;
+
+    WebView* webView = kit(m_mediaElement->document()->page());
+    HWND parentHwnd = webView ? webView->viewWindow() : 0;
+
+    m_fullscreenWindow->createWindow(parentHwnd);
+#if USE(ACCELERATED_COMPOSITING)
+    m_fullscreenWindow->setRootChildLayer(m_rootChild);
+
+    WKCACFLayer* videoLayer = m_mediaElement->player()->platformLayer();
+    m_rootChild->addSublayer(videoLayer);
+    m_rootChild->setNeedsLayout();
+#endif
+
+    RECT windowRect;
+    GetClientRect(m_fullscreenWindow->hwnd(), &windowRect);
+    m_fullscreenSize.setWidth(windowRect.right - windowRect.left);
+    m_fullscreenSize.setHeight(windowRect.bottom - windowRect.top);
+
+    createHUDWindow();
+}
+
+void FullscreenVideoController::exitFullscreen()
+{
+    SetWindowLongPtr(m_hudWindow, 0, 0);
+
+    if (m_fullscreenWindow)
+        m_fullscreenWindow = 0;
+
+    ASSERT(!IsWindow(m_hudWindow));
+    m_hudWindow = 0;
+
+    // We previously ripped the mediaElement's platform layer out
+    // of its orginial layer tree to display it in our fullscreen
+    // window.  Now, we need to get the layer back in its original
+    // tree.
+    // 
+    // As a side effect of setting the player to invisible/visible,
+    // the player's layer will be recreated, and will be picked up 
+    // the next time the layer tree is synched.
+    m_mediaElement->player()->setVisible(0);
+    m_mediaElement->player()->setVisible(1);
+}
+
+bool FullscreenVideoController::canPlay() const
+{
+    return m_mediaElement && m_mediaElement->canPlay();
+}
+
+void FullscreenVideoController::play()
+{
+    if (m_mediaElement)
+        m_mediaElement->play(m_mediaElement->processingUserGesture());
+}
+
+void FullscreenVideoController::pause()
+{
+    if (m_mediaElement)
+        m_mediaElement->pause(m_mediaElement->processingUserGesture());
+}
+
+float FullscreenVideoController::volume() const
+{
+    return m_mediaElement ? m_mediaElement->volume() : 0;
+}
+
+void FullscreenVideoController::setVolume(float volume)
+{
+    if (m_mediaElement) {
+        ExceptionCode ec;
+        m_mediaElement->setVolume(volume, ec);
+    }
+}
+
+float FullscreenVideoController::currentTime() const
+{
+    return m_mediaElement ? m_mediaElement->currentTime() : 0;
+}
+
+void FullscreenVideoController::setCurrentTime(float value)
+{
+    if (m_mediaElement) {
+        ExceptionCode ec;
+        m_mediaElement->setCurrentTime(value, ec);
+    }
+}
+
+float FullscreenVideoController::duration() const
+{
+    return m_mediaElement ? m_mediaElement->duration() : 0;
+}
+
+void FullscreenVideoController::beginScrubbing()
+{
+    if (m_mediaElement) 
+        m_mediaElement->beginScrubbing();
+}
+
+void FullscreenVideoController::endScrubbing()
+{
+    if (m_mediaElement) 
+        m_mediaElement->endScrubbing();
+}
+
+LRESULT FullscreenVideoController::fullscreenClientWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    switch (message) {
+    case WM_CHAR:
+        onChar(wParam);
+        break;
+    case WM_KEYDOWN:
+        onKeyDown(wParam);
+        break;
+    case WM_LBUTTONDOWN:
+        onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    case WM_MOUSEMOVE:
+        onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    case WM_LBUTTONUP:
+        onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    }
+
+    return DefWindowProc(wnd, message, wParam, lParam);
+}
+
+static const LPCWSTR fullscreenVideeoHUDWindowClassName = L"fullscreenVideeoHUDWindowClass";
+
+void FullscreenVideoController::registerHUDWindowClass()
+{
+    static bool haveRegisteredHUDWindowClass;
+    if (haveRegisteredHUDWindowClass)
+        return;
+
+    haveRegisteredHUDWindowClass = true;
+
+    WNDCLASSEX wcex;
+
+    wcex.cbSize = sizeof(WNDCLASSEX);
+
+    wcex.style = CS_HREDRAW | CS_VREDRAW;
+    wcex.lpfnWndProc = hudWndProc;
+    wcex.cbClsExtra = 0;
+    wcex.cbWndExtra = 4;
+    wcex.hInstance = gInstance;
+    wcex.hIcon = 0;
+    wcex.hCursor = LoadCursor(0, IDC_ARROW);
+    wcex.hbrBackground = 0;
+    wcex.lpszMenuName = 0;
+    wcex.lpszClassName = fullscreenVideeoHUDWindowClassName;
+    wcex.hIconSm = 0;
+
+    RegisterClassEx(&wcex);
+}
+
+void FullscreenVideoController::createHUDWindow()
+{
+    m_hudPosition.setX((m_fullscreenSize.width() - windowWidth) / 2);
+    m_hudPosition.setY(m_fullscreenSize.height() * initialHUDPositionY - windowHeight / 2);
+
+    // Local variable that will hold the returned pixels. No need to cleanup this value. It
+    // will get cleaned up when m_bitmap is destroyed in the dtor
+    void* pixels;
+    BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(IntSize(windowWidth, windowHeight));
+    m_bitmap.set(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0));
+    
+    // Dirty the window so the HUD draws
+    RECT clearRect = { m_hudPosition.x(), m_hudPosition.y(), m_hudPosition.x() + windowWidth, m_hudPosition.y() + windowHeight };
+    InvalidateRect(m_fullscreenWindow->hwnd(), &clearRect, true);
+
+    m_playPauseButton.setShowAltButton(!canPlay());
+    m_volumeSlider.setValue(volume());
+    m_timeSlider.setValue(currentTime() / duration());
+
+    if (!canPlay())
+        m_timer.startRepeating(timerInterval);
+
+    registerHUDWindowClass();
+
+    m_hudWindow = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, 
+        fullscreenVideeoHUDWindowClassName, 0, WS_POPUP | WS_VISIBLE,
+        m_hudPosition.x(), m_hudPosition.y(), 0, 0, m_fullscreenWindow->hwnd(), 0, gInstance, 0);
+    ASSERT(::IsWindow(m_hudWindow));
+    SetWindowLongPtr(m_hudWindow, 0, reinterpret_cast<LONG_PTR>(this));
+
+    draw();
+}
+
+static String timeToString(float time)
+{
+    if (!isfinite(time))
+        time = 0;
+    int seconds = fabsf(time); 
+    int hours = seconds / (60 * 60);
+    int minutes = (seconds / 60) % 60;
+    seconds %= 60;
+
+    if (hours) {
+        if (hours > 9)
+            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
+        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
+    }
+
+    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
+}
+
+void FullscreenVideoController::draw()
+{
+    HDC windowDC = GetDC(m_hudWindow);
+    HDC bitmapDC = CreateCompatibleDC(windowDC);
+    ::ReleaseDC(m_hudWindow, windowDC);
+    SelectObject(bitmapDC, m_bitmap.get());
+
+    GraphicsContext context(bitmapDC, true);
+
+    context.save();
+
+    // Draw the background
+    IntSize outerRadius(borderRadius, borderRadius);
+    IntRect outerRect(0, 0, windowWidth, windowHeight);
+    IntSize innerRadius(borderRadius - borderThickness, borderRadius - borderThickness);
+    IntRect innerRect(borderThickness, borderThickness, windowWidth - borderThickness * 2, windowHeight - borderThickness * 2);
+
+    context.fillRoundedRect(outerRect, outerRadius, outerRadius, outerRadius, outerRadius, Color(borderColor), DeviceColorSpace);
+    context.setCompositeOperation(CompositeCopy);
+    context.fillRoundedRect(innerRect, innerRadius, innerRadius, innerRadius, innerRadius, Color(backgroundColor), DeviceColorSpace);
+
+    // Draw the widgets
+    m_playPauseButton.draw(context);
+    m_volumeUpButton.draw(context);
+    m_volumeSliderButton.draw(context);
+    m_volumeDownButton.draw(context);
+    m_timeSliderButton.draw(context);
+    m_exitFullscreenButton.draw(context);
+    m_volumeSlider.draw(context);
+    m_timeSlider.draw(context);
+
+    // Draw the text strings
+    FontDescription desc;
+
+    NONCLIENTMETRICS metrics;
+    metrics.cbSize = sizeof(metrics);
+    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
+    FontFamily family;
+    family.setFamily(metrics.lfSmCaptionFont.lfFaceName);
+    desc.setFamily(family);
+
+    desc.setComputedSize(textSize);
+    Font font = Font(desc, 0, 0);
+    font.update(0);
+
+    String s;
+
+    // The y positioning of these two text strings is tricky because they are so small. They
+    // are currently positioned relative to the center of the slider and then down the font
+    // height / 4 (which is actually half of font height /2), which positions the center of
+    // the text at the center of the slider.
+    // Left string
+    s = timeToString(currentTime());
+    TextRun leftText(s);
+    context.setFillColor(Color(textColor), DeviceColorSpace);
+    context.drawText(font, leftText, IntPoint(windowWidth / 2 - timeSliderWidth / 2 - margin - font.width(leftText), windowHeight - margin - sliderHeight / 2 + font.height() / 4));
+
+    // Right string
+    s = timeToString(currentTime() - duration());
+    TextRun rightText(s);
+    context.setFillColor(Color(textColor), DeviceColorSpace);
+    context.drawText(font, rightText, IntPoint(windowWidth / 2 + timeSliderWidth / 2 + margin, windowHeight - margin - sliderHeight / 2 + font.height() / 4));
+
+    // Copy to the window
+    BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+    SIZE size = { windowWidth, windowHeight };
+    POINT sourcePoint = {0, 0};
+    POINT destPoint = { m_hudPosition.x(), m_hudPosition.y() };
+    BOOL result = UpdateLayeredWindow(m_hudWindow, 0, &destPoint, &size, bitmapDC, &sourcePoint, 0, &blendFunction, ULW_ALPHA);
+
+    context.restore();
+
+    ::DeleteDC(bitmapDC);
+}
+
+LRESULT FullscreenVideoController::hudWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    LONG_PTR longPtr = GetWindowLongPtr(wnd, 0);
+    FullscreenVideoController* controller = reinterpret_cast<FullscreenVideoController*>(longPtr);
+    if (!controller)
+        return DefWindowProc(wnd, message, wParam, lParam);
+
+    switch (message) {
+    case WM_CHAR:
+        controller->onChar(wParam);
+        break;
+    case WM_KEYDOWN:
+        controller->onKeyDown(wParam);
+        break;
+    case WM_LBUTTONDOWN:
+        controller->onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    case WM_MOUSEMOVE:
+        controller->onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    case WM_LBUTTONUP:
+        controller->onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
+        break;
+    }
+
+    return DefWindowProc(wnd, message, wParam, lParam);
+}
+
+void FullscreenVideoController::onChar(int c)
+{
+    if (c == VK_ESCAPE) {
+        if (m_mediaElement)
+            m_mediaElement->exitFullscreen();
+    } else if (c == VK_SPACE)
+        togglePlay();
+}
+
+void FullscreenVideoController::onKeyDown(int virtualKey)
+{
+    if (virtualKey == VK_ESCAPE) {
+        if (m_mediaElement)
+            m_mediaElement->exitFullscreen();
+    }
+}
+
+void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*)
+{
+    // Update the time slider
+    m_timeSlider.setValue(currentTime() / duration());
+    draw();
+}
+
+void FullscreenVideoController::onMouseDown(const IntPoint& point)
+{
+    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
+
+    // Don't bother hit testing if we're outside the bounds of the window
+    if (convertedPoint.x() < 0 || convertedPoint.x() >= windowWidth || convertedPoint.y() < 0 || convertedPoint.y() >= windowHeight)
+        return;
+
+    m_hitWidget = 0;
+    m_movingWindow = false;
+
+    if (m_playPauseButton.hitTest(convertedPoint))
+        m_hitWidget = &m_playPauseButton;
+    else if (m_exitFullscreenButton.hitTest(convertedPoint))
+        m_hitWidget = &m_exitFullscreenButton;
+    else if (m_volumeUpButton.hitTest(convertedPoint))
+        m_hitWidget = &m_volumeUpButton;
+    else if (m_volumeDownButton.hitTest(convertedPoint))
+        m_hitWidget = &m_volumeDownButton;
+    else if (m_volumeSlider.hitTest(convertedPoint)) {
+        m_hitWidget = &m_volumeSlider;
+        m_volumeSlider.drag(convertedPoint, true);
+        setVolume(m_volumeSlider.value());
+    } else if (m_timeSlider.hitTest(convertedPoint)) {
+        m_hitWidget = &m_timeSlider;
+        m_timeSlider.drag(convertedPoint, true);
+        beginScrubbing();
+        setCurrentTime(m_timeSlider.value() * duration());
+    }
+
+    // If we did not pick any of our widgets we are starting a window move
+    if (!m_hitWidget) {
+        m_moveOffset = convertedPoint;
+        m_movingWindow = true;
+    }
+
+    draw();
+}
+
+void FullscreenVideoController::onMouseMove(const IntPoint& point)
+{
+    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
+
+    if (m_hitWidget) {
+        m_hitWidget->drag(convertedPoint, false);
+        if (m_hitWidget == &m_volumeSlider)
+            setVolume(m_volumeSlider.value());
+        else if (m_hitWidget == &m_timeSlider)
+            setCurrentTime(m_timeSlider.value() * duration());
+        draw();
+    } else if (m_movingWindow)
+        m_hudPosition.move(convertedPoint.x() - m_moveOffset.x(), convertedPoint.y() - m_moveOffset.y());
+}
+
+void FullscreenVideoController::onMouseUp(const IntPoint& point)
+{
+    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
+    m_movingWindow = false;
+
+    if (m_hitWidget) {
+        if (m_hitWidget == &m_playPauseButton && m_playPauseButton.hitTest(convertedPoint))
+            togglePlay();
+        else if (m_hitWidget == &m_volumeUpButton && m_volumeUpButton.hitTest(convertedPoint)) {
+            setVolume(1);
+            m_volumeSlider.setValue(1);
+        } else if (m_hitWidget == &m_volumeDownButton && m_volumeDownButton.hitTest(convertedPoint)) {
+            setVolume(0);
+            m_volumeSlider.setValue(0);
+        } else if (m_hitWidget == &m_timeSlider)
+            endScrubbing();
+        else if (m_hitWidget == &m_exitFullscreenButton && m_exitFullscreenButton.hitTest(convertedPoint)) {
+            m_hitWidget = 0;
+            if (m_mediaElement)
+                m_mediaElement->exitFullscreen();
+            return;
+        }
+    }
+
+    m_hitWidget = 0;
+    draw();
+}
+
+void FullscreenVideoController::togglePlay()
+{
+    if (canPlay())
+        play();
+    else
+        pause();
+
+    m_playPauseButton.setShowAltButton(!canPlay());
+
+    // Run a timer while the video is playing so we can keep the time
+    // slider and time values up to date.
+    if (!canPlay())
+        m_timer.startRepeating(timerInterval);
+    else
+        m_timer.stop();
+
+    draw();
+}
+
+#endif