1 /* 2 * Copyright 2009, 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 #include "config.h" 27 #include "RenderThemeAndroid.h" 28 29 #include "Color.h" 30 #include "Element.h" 31 #include "GraphicsContext.h" 32 #include "HTMLNames.h" 33 #include "HTMLOptionElement.h" 34 #include "HTMLSelectElement.h" 35 #include "Node.h" 36 #include "PlatformGraphicsContext.h" 37 #if ENABLE(VIDEO) 38 #include "RenderMediaControls.h" 39 #endif 40 #include "RenderSkinAndroid.h" 41 #include "RenderSkinButton.h" 42 #include "RenderSkinCombo.h" 43 #include "RenderSkinMediaButton.h" 44 #include "RenderSkinRadio.h" 45 #include "SkCanvas.h" 46 #include "UserAgentStyleSheets.h" 47 #include "WebCoreFrameBridge.h" 48 49 namespace WebCore { 50 51 // Add padding to the fontSize of ListBoxes to get their maximum sizes. 52 // Listboxes often have a specified size. Since we change them into 53 // dropdowns, we want a much smaller height, which encompasses the text. 54 const int listboxPadding = 5; 55 56 // This is the color of selection in a textfield. It was computed from 57 // frameworks/base/core/res/res/values/colors.xml, which uses #9983CC39 58 // (decimal a = 153, r = 131, g = 204, b = 57) 59 // for all four highlighted text values. Blending this with white yields: 60 // R = (131 * 153 + 255 * (255 - 153)) / 255 -> 180.6 61 // G = (204 * 153 + 255 * (255 - 153)) / 255 -> 224.4 62 // B = ( 57 * 153 + 255 * (255 - 153)) / 255 -> 136.2 63 64 const RGBA32 selectionColor = makeRGB(181, 224, 136); 65 66 static SkCanvas* getCanvasFromInfo(const PaintInfo& info) 67 { 68 return info.context->platformContext()->mCanvas; 69 } 70 71 static android::WebFrame* getWebFrame(const Node* node) 72 { 73 if (!node) 74 return 0; 75 return android::WebFrame::getWebFrame(node->document()->frame()); 76 } 77 78 RenderTheme* theme() 79 { 80 DEFINE_STATIC_LOCAL(RenderThemeAndroid, androidTheme, ()); 81 return &androidTheme; 82 } 83 84 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) 85 { 86 static RenderTheme* rt = RenderThemeAndroid::create().releaseRef(); 87 return rt; 88 } 89 90 PassRefPtr<RenderTheme> RenderThemeAndroid::create() 91 { 92 return adoptRef(new RenderThemeAndroid()); 93 } 94 95 RenderThemeAndroid::RenderThemeAndroid() 96 { 97 } 98 99 RenderThemeAndroid::~RenderThemeAndroid() 100 { 101 } 102 103 void RenderThemeAndroid::close() 104 { 105 } 106 107 bool RenderThemeAndroid::stateChanged(RenderObject* obj, ControlState state) const 108 { 109 if (CheckedState == state) { 110 obj->repaint(); 111 return true; 112 } 113 return false; 114 } 115 116 Color RenderThemeAndroid::platformActiveSelectionBackgroundColor() const 117 { 118 return Color(selectionColor); 119 } 120 121 Color RenderThemeAndroid::platformInactiveSelectionBackgroundColor() const 122 { 123 return Color(Color::transparent); 124 } 125 126 Color RenderThemeAndroid::platformActiveSelectionForegroundColor() const 127 { 128 return Color::black; 129 } 130 131 Color RenderThemeAndroid::platformInactiveSelectionForegroundColor() const 132 { 133 return Color::black; 134 } 135 136 Color RenderThemeAndroid::platformTextSearchHighlightColor() const 137 { 138 return Color(Color::transparent); 139 } 140 141 Color RenderThemeAndroid::platformActiveListBoxSelectionBackgroundColor() const 142 { 143 return Color(Color::transparent); 144 } 145 146 Color RenderThemeAndroid::platformInactiveListBoxSelectionBackgroundColor() const 147 { 148 return Color(Color::transparent); 149 } 150 151 Color RenderThemeAndroid::platformActiveListBoxSelectionForegroundColor() const 152 { 153 return Color(Color::transparent); 154 } 155 156 Color RenderThemeAndroid::platformInactiveListBoxSelectionForegroundColor() const 157 { 158 return Color(Color::transparent); 159 } 160 161 int RenderThemeAndroid::baselinePosition(const RenderObject* obj) const 162 { 163 // From the description of this function in RenderTheme.h: 164 // A method to obtain the baseline position for a "leaf" control. This will only be used if a baseline 165 // position cannot be determined by examining child content. Checkboxes and radio buttons are examples of 166 // controls that need to do this. 167 // 168 // Our checkboxes and radio buttons need to be offset to line up properly. 169 return RenderTheme::baselinePosition(obj) - 2; 170 } 171 172 void RenderThemeAndroid::addIntrinsicMargins(RenderStyle* style) const 173 { 174 // Cut out the intrinsic margins completely if we end up using a small font size 175 if (style->fontSize() < 11) 176 return; 177 178 // Intrinsic margin value. 179 const int m = 2; 180 181 // FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed. 182 if (style->width().isIntrinsicOrAuto()) { 183 if (style->marginLeft().quirk()) 184 style->setMarginLeft(Length(m, Fixed)); 185 if (style->marginRight().quirk()) 186 style->setMarginRight(Length(m, Fixed)); 187 } 188 189 if (style->height().isAuto()) { 190 if (style->marginTop().quirk()) 191 style->setMarginTop(Length(m, Fixed)); 192 if (style->marginBottom().quirk()) 193 style->setMarginBottom(Length(m, Fixed)); 194 } 195 } 196 197 bool RenderThemeAndroid::supportsFocus(ControlPart appearance) 198 { 199 switch (appearance) { 200 case PushButtonPart: 201 case ButtonPart: 202 case TextFieldPart: 203 return true; 204 default: 205 return false; 206 } 207 208 return false; 209 } 210 211 void RenderThemeAndroid::adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const 212 { 213 // Code is taken from RenderThemeSafari.cpp 214 // It makes sure we have enough space for the button text. 215 const int paddingHoriz = 12; 216 const int paddingVert = 8; 217 style->setPaddingLeft(Length(paddingHoriz, Fixed)); 218 style->setPaddingRight(Length(paddingHoriz, Fixed)); 219 style->setPaddingTop(Length(paddingVert, Fixed)); 220 style->setPaddingBottom(Length(paddingVert, Fixed)); 221 222 // Set a min-height so that we can't get smaller than the mini button. 223 style->setMinHeight(Length(15, Fixed)); 224 } 225 226 bool RenderThemeAndroid::paintCheckbox(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 227 { 228 RenderSkinRadio::Draw(getCanvasFromInfo(info), obj->node(), rect, true); 229 return false; 230 } 231 232 bool RenderThemeAndroid::paintButton(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 233 { 234 // If it is a disabled button, simply paint it to the master picture. 235 Node* node = obj->node(); 236 Element* formControlElement = static_cast<Element*>(node); 237 if (formControlElement) { 238 android::WebFrame* webFrame = getWebFrame(node); 239 if (webFrame) { 240 RenderSkinAndroid* skins = webFrame->renderSkins(); 241 if (skins) { 242 RenderSkinAndroid::State state = RenderSkinAndroid::kNormal; 243 if (!formControlElement->isEnabledFormControl()) 244 state = RenderSkinAndroid::kDisabled; 245 skins->renderSkinButton()->draw(getCanvasFromInfo(info), rect, state); 246 } 247 } 248 } 249 250 // We always return false so we do not request to be redrawn. 251 return false; 252 } 253 254 #if ENABLE(VIDEO) 255 256 String RenderThemeAndroid::extraMediaControlsStyleSheet() 257 { 258 return String(mediaControlsAndroidUserAgentStyleSheet, sizeof(mediaControlsAndroidUserAgentStyleSheet)); 259 } 260 261 bool RenderThemeAndroid::shouldRenderMediaControlPart(ControlPart part, Element* e) 262 { 263 HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(e); 264 switch (part) { 265 case MediaMuteButtonPart: 266 return false; 267 case MediaSeekBackButtonPart: 268 case MediaSeekForwardButtonPart: 269 return false; 270 case MediaRewindButtonPart: 271 return mediaElement->movieLoadType() != MediaPlayer::LiveStream; 272 case MediaReturnToRealtimeButtonPart: 273 return mediaElement->movieLoadType() == MediaPlayer::LiveStream; 274 case MediaFullscreenButtonPart: 275 return mediaElement->supportsFullscreen(); 276 case MediaToggleClosedCaptionsButtonPart: 277 return mediaElement->hasClosedCaptions(); 278 default: 279 return true; 280 } 281 } 282 283 bool RenderThemeAndroid::paintMediaFullscreenButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 284 { 285 bool translucent = false; 286 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 287 translucent = true; 288 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::FULLSCREEN, translucent); 289 return false; 290 } 291 292 bool RenderThemeAndroid::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 293 { 294 bool translucent = false; 295 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 296 translucent = true; 297 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::MUTE, translucent); 298 return false; 299 } 300 301 bool RenderThemeAndroid::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 302 { 303 bool translucent = false; 304 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 305 translucent = true; 306 if (MediaControlPlayButtonElement* btn = static_cast<MediaControlPlayButtonElement*>(o->node())) { 307 if (btn->displayType() == MediaPlayButton) 308 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::PLAY, translucent); 309 else 310 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::PAUSE, translucent); 311 return false; 312 } 313 return true; 314 } 315 316 bool RenderThemeAndroid::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 317 { 318 bool translucent = false; 319 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 320 translucent = true; 321 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::REWIND, translucent); 322 return false; 323 } 324 325 bool RenderThemeAndroid::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 326 { 327 bool translucent = false; 328 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 329 translucent = true; 330 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::FORWARD, translucent); 331 return false; 332 } 333 334 bool RenderThemeAndroid::paintMediaControlsBackground(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 335 { 336 bool translucent = false; 337 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 338 translucent = true; 339 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::BACKGROUND_SLIDER, translucent); 340 return false; 341 } 342 343 bool RenderThemeAndroid::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 344 { 345 bool translucent = false; 346 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 347 translucent = true; 348 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, 349 RenderSkinMediaButton::SLIDER_TRACK, translucent, o); 350 return false; 351 } 352 353 bool RenderThemeAndroid::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) 354 { 355 bool translucent = false; 356 if (o && toParentMediaElement(o) && toParentMediaElement(o)->hasTagName(HTMLNames::videoTag)) 357 translucent = true; 358 RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::SLIDER_THUMB, translucent); 359 return false; 360 } 361 362 void RenderThemeAndroid::adjustSliderThumbSize(RenderObject* o) const 363 { 364 static const int sliderThumbWidth = RenderSkinMediaButton::sliderThumbWidth(); 365 static const int sliderThumbHeight = RenderSkinMediaButton::sliderThumbHeight(); 366 if (o->style()->appearance() == MediaSliderThumbPart) { 367 o->style()->setWidth(Length(sliderThumbWidth, Fixed)); 368 o->style()->setHeight(Length(sliderThumbHeight, Fixed)); 369 } 370 } 371 372 #endif 373 374 bool RenderThemeAndroid::paintRadio(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 375 { 376 RenderSkinRadio::Draw(getCanvasFromInfo(info), obj->node(), rect, false); 377 return false; 378 } 379 380 void RenderThemeAndroid::setCheckboxSize(RenderStyle* style) const 381 { 382 style->setWidth(Length(19, Fixed)); 383 style->setHeight(Length(19, Fixed)); 384 } 385 386 void RenderThemeAndroid::setRadioSize(RenderStyle* style) const 387 { 388 // This is the same as checkboxes. 389 setCheckboxSize(style); 390 } 391 392 void RenderThemeAndroid::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const 393 { 394 addIntrinsicMargins(style); 395 } 396 397 bool RenderThemeAndroid::paintTextField(RenderObject*, const PaintInfo&, const IntRect&) 398 { 399 return true; 400 } 401 402 void RenderThemeAndroid::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const 403 { 404 addIntrinsicMargins(style); 405 } 406 407 bool RenderThemeAndroid::paintTextArea(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 408 { 409 if (obj->isMenuList()) 410 paintCombo(obj, info, rect); 411 return true; 412 } 413 414 void RenderThemeAndroid::adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const 415 { 416 addIntrinsicMargins(style); 417 } 418 419 bool RenderThemeAndroid::paintSearchField(RenderObject*, const PaintInfo&, const IntRect&) 420 { 421 return true; 422 } 423 424 static void adjustMenuListStyleCommon(RenderStyle* style) 425 { 426 // Added to make room for our arrow and make the touch target less cramped. 427 style->setPaddingLeft(Length(RenderSkinCombo::padding(), Fixed)); 428 style->setPaddingTop(Length(RenderSkinCombo::padding(), Fixed)); 429 style->setPaddingBottom(Length(RenderSkinCombo::padding(), Fixed)); 430 style->setPaddingRight(Length(RenderSkinCombo::extraWidth(), Fixed)); 431 style->setMinHeight(Length(RenderSkinCombo::minHeight(), Fixed)); 432 } 433 434 void RenderThemeAndroid::adjustListboxStyle(CSSStyleSelector*, RenderStyle* style, Element*) const 435 { 436 adjustMenuListButtonStyle(0, style, 0); 437 } 438 439 void RenderThemeAndroid::adjustMenuListStyle(CSSStyleSelector*, RenderStyle* style, Element* e) const 440 { 441 adjustMenuListStyleCommon(style); 442 addIntrinsicMargins(style); 443 } 444 445 bool RenderThemeAndroid::paintCombo(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 446 { 447 if (obj->style() && !obj->style()->visitedDependentColor(CSSPropertyBackgroundColor).alpha()) 448 return true; 449 return RenderSkinCombo::Draw(getCanvasFromInfo(info), obj->node(), rect.x(), rect.y(), rect.width(), rect.height()); 450 } 451 452 bool RenderThemeAndroid::paintMenuList(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 453 { 454 return paintCombo(obj, info, rect); 455 } 456 457 void RenderThemeAndroid::adjustMenuListButtonStyle(CSSStyleSelector*, 458 RenderStyle* style, Element*) const 459 { 460 // Copied from RenderThemeSafari. 461 const float baseFontSize = 11.0f; 462 const int baseBorderRadius = 5; 463 float fontScale = style->fontSize() / baseFontSize; 464 465 style->resetPadding(); 466 style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up? 467 468 const int minHeight = 15; 469 style->setMinHeight(Length(minHeight, Fixed)); 470 471 style->setLineHeight(RenderStyle::initialLineHeight()); 472 // Found these padding numbers by trial and error. 473 const int padding = 4; 474 style->setPaddingTop(Length(padding, Fixed)); 475 style->setPaddingLeft(Length(padding, Fixed)); 476 adjustMenuListStyleCommon(style); 477 } 478 479 bool RenderThemeAndroid::paintMenuListButton(RenderObject* obj, const PaintInfo& info, const IntRect& rect) 480 { 481 return paintCombo(obj, info, rect); 482 } 483 484 bool RenderThemeAndroid::supportsFocusRing(const RenderStyle* style) const 485 { 486 return style->opacity() > 0 487 && style->hasAppearance() 488 && style->appearance() != TextFieldPart 489 && style->appearance() != SearchFieldPart 490 && style->appearance() != TextAreaPart 491 && style->appearance() != CheckboxPart 492 && style->appearance() != RadioPart 493 && style->appearance() != PushButtonPart 494 && style->appearance() != SquareButtonPart 495 && style->appearance() != ButtonPart 496 && style->appearance() != ButtonBevelPart 497 && style->appearance() != MenulistPart 498 && style->appearance() != MenulistButtonPart; 499 } 500 501 } // namespace WebCore 502