1/*
2 * Copyright 2010, The Android Open Source Project
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 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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#define LOG_TAG "WebCore"
27
28#include "config.h"
29#include "android_graphics.h"
30#include "Document.h"
31#include "IntRect.h"
32#include "Node.h"
33#include "RenderObject.h"
34#include "RenderSkinMediaButton.h"
35#include "RenderSlider.h"
36#include "SkCanvas.h"
37#include "SkNinePatch.h"
38#include "SkRect.h"
39#include <androidfw/AssetManager.h>
40#include <utils/Debug.h>
41#include <utils/Log.h>
42#include <wtf/text/CString.h>
43
44extern android::AssetManager* globalAssetManager();
45
46struct PatchData {
47    const char* name;
48    int8_t outset, margin;
49};
50
51static const PatchData gFiles[] =
52    {
53        { "scrubber_primary_holo.9.png", 0, 0 }, // SLIDER_TRACK, left of the SLIDER_THUMB
54        { "ic_media_pause.png", 0, 0}, // PAUSE
55        { "ic_media_play.png", 0, 0 }, // PLAY
56        { "ic_media_pause.png", 0, 0 }, // MUTE
57        { "ic_media_rew.png", 0, 0 }, // REWIND
58        { "ic_media_ff.png", 0, 0 }, // FORWARD
59        { "ic_media_fullscreen.png", 0, 0 }, // FULLSCREEN
60        { "spinner_76_outer_holo.png", 0, 0 }, // SPINNER_OUTER
61        { "spinner_76_inner_holo.png", 0, 0 }, // SPINNER_INNER
62        { "ic_media_video_poster.png", 0, 0 }, // VIDEO
63        { "btn_media_player_disabled.9.png", 0, 0 }, // BACKGROUND_SLIDER
64        { "scrubber_track_holo_dark.9.png", 0, 0 },  // SLIDER_TRACK
65        { "scrubber_control_normal_holo.png", 0, 0 }      // SLIDER_THUMB
66    };
67
68static SkBitmap gButton[sizeof(gFiles)/sizeof(gFiles[0])];
69static bool gDecoded;
70static bool gDecodingFailed;
71
72namespace WebCore {
73
74void RenderSkinMediaButton::Decode()
75{
76    String drawableDirectory = RenderSkinAndroid::DrawableDirectory();
77
78    gDecoded = true;
79    gDecodingFailed = false;
80    android::AssetManager* am = globalAssetManager();
81    for (size_t i = 0; i < sizeof(gFiles)/sizeof(gFiles[0]); i++) {
82        String path = drawableDirectory + gFiles[i].name;
83        if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &gButton[i])) {
84            gDecodingFailed = true;
85            ALOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw");
86            break;
87        }
88    }
89}
90
91void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonType,
92                                 bool translucent, RenderObject* o, bool drawBackground)
93{
94    if (!gDecoded) {
95        Decode();
96    }
97
98    if (!canvas)
99        return;
100
101    // If we failed to decode, do nothing.  This way the browser still works,
102    // and webkit will still draw the label and layout space for us.
103    if (gDecodingFailed)
104        return;
105
106    bool drawsNinePatch = false;
107    bool drawsImage = true;
108
109    int ninePatchIndex = 0;
110    int imageIndex = 0;
111
112    SkRect bounds(r);
113    SkScalar imageMargin = 8;
114    SkPaint paint;
115
116    int alpha = 255;
117    if (translucent)
118        alpha = 190;
119
120    SkColor backgroundColor = SkColorSetARGB(alpha, 34, 34, 34);
121    SkColor trackBackgroundColor = SkColorSetARGB(255, 100, 100, 100);
122    paint.setColor(backgroundColor);
123    paint.setFlags(SkPaint::kFilterBitmap_Flag);
124
125    switch (buttonType) {
126    case PAUSE:
127    case PLAY:
128    case MUTE:
129    case REWIND:
130    case FORWARD:
131    case FULLSCREEN:
132    {
133         imageIndex = buttonType + 1;
134         paint.setColor(backgroundColor);
135         break;
136    }
137    case SPINNER_OUTER:
138    case SPINNER_INNER:
139    case VIDEO:
140    {
141         imageIndex = buttonType + 1;
142         break;
143    }
144    case BACKGROUND_SLIDER:
145    {
146         drawsImage = false;
147         break;
148    }
149    case SLIDER_TRACK:
150    {
151         drawsNinePatch = true;
152         drawsImage = false;
153         ninePatchIndex = buttonType + 1;
154         break;
155    }
156    case SLIDER_THUMB:
157    {
158         imageMargin = 0;
159         imageIndex = buttonType + 1;
160         break;
161    }
162    default:
163         return;
164    }
165
166    if (drawBackground) {
167        canvas->drawRect(r, paint);
168    }
169
170    if (drawsNinePatch) {
171        const PatchData& pd = gFiles[ninePatchIndex];
172        int marginValue = pd.margin + pd.outset;
173
174        SkIRect margin;
175        margin.set(marginValue, marginValue, marginValue, marginValue);
176        if (buttonType == SLIDER_TRACK) {
177            // Cut the height in half (with some extra slop determined by trial
178            // and error to get the placement just right.
179            SkScalar quarterHeight = SkScalarHalf(SkScalarHalf(bounds.height()));
180            bounds.fTop += quarterHeight + SkScalarHalf(3);
181            bounds.fBottom += -quarterHeight + SK_ScalarHalf;
182            if (o && o->isSlider()) {
183                RenderSlider* slider = toRenderSlider(o);
184                IntRect thumb = slider->thumbRect();
185                // Inset the track by half the width of the thumb, so the track
186                // does not appear to go beyond the space where the thumb can
187                // be.
188                SkScalar thumbHalfWidth = SkIntToScalar(thumb.width()/2);
189                bounds.fLeft += thumbHalfWidth;
190                bounds.fRight -= thumbHalfWidth;
191                if (thumb.x() > 0) {
192                    // The video is past the starting point.  Show the area to
193                    // left of the thumb as having been played.
194                    SkScalar alreadyPlayed = SkIntToScalar(thumb.center().x() + r.x());
195                    SkRect playedRect(bounds);
196                    playedRect.fRight = alreadyPlayed;
197                    SkNinePatch::DrawNine(canvas, playedRect, gButton[0], margin);
198                    bounds.fLeft = alreadyPlayed;
199                }
200
201            }
202        }
203        SkNinePatch::DrawNine(canvas, bounds, gButton[ninePatchIndex], margin);
204    }
205
206    if (drawsImage) {
207        SkScalar SIZE = gButton[imageIndex].width();
208        SkScalar width = r.width();
209        SkScalar scale = SkScalarDiv(width - 2*imageMargin, SIZE);
210        int saveScaleCount = canvas->save();
211        canvas->translate(bounds.fLeft + imageMargin, bounds.fTop + imageMargin);
212        canvas->scale(scale, scale);
213        canvas->drawBitmap(gButton[imageIndex], 0, 0, &paint);
214        canvas->restoreToCount(saveScaleCount);
215    }
216}
217
218} // WebCore
219