/* * Copyright 2009, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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" #include "RenderThemeAndroid.h" #include "Color.h" #include "Element.h" #include "GraphicsContext.h" #include "HTMLNames.h" #include "HTMLOptionElement.h" #include "HTMLSelectElement.h" #include "Node.h" #include "PlatformGraphicsContext.h" #if ENABLE(VIDEO) #include "RenderMediaControls.h" #endif #include "RenderSkinAndroid.h" #include "RenderSkinButton.h" #include "RenderSkinCombo.h" #include "RenderSkinMediaButton.h" #include "RenderSkinRadio.h" #include "SkCanvas.h" #include "UserAgentStyleSheets.h" namespace WebCore { // Add padding to the fontSize of ListBoxes to get their maximum sizes. // Listboxes often have a specified size. Since we change them into // dropdowns, we want a much smaller height, which encompasses the text. const int listboxPadding = 5; // This is the color of selection in a textfield. It was obtained by checking // the color of selection in TextViews in the system. const RGBA32 selectionColor = makeRGB(255, 146, 0); static SkCanvas* getCanvasFromInfo(const PaintInfo& info) { return info.context->platformContext()->mCanvas; } RenderTheme* theme() { DEFINE_STATIC_LOCAL(RenderThemeAndroid, androidTheme, ()); return &androidTheme; } PassRefPtr RenderTheme::themeForPage(Page* page) { static RenderTheme* rt = RenderThemeAndroid::create().releaseRef(); return rt; } PassRefPtr RenderThemeAndroid::create() { return adoptRef(new RenderThemeAndroid()); } RenderThemeAndroid::RenderThemeAndroid() { } RenderThemeAndroid::~RenderThemeAndroid() { } void RenderThemeAndroid::close() { } bool RenderThemeAndroid::stateChanged(RenderObject* obj, ControlState state) const { if (CheckedState == state) { obj->repaint(); return true; } return false; } Color RenderThemeAndroid::platformActiveSelectionBackgroundColor() const { return Color(selectionColor); } Color RenderThemeAndroid::platformInactiveSelectionBackgroundColor() const { return Color(Color::transparent); } Color RenderThemeAndroid::platformActiveSelectionForegroundColor() const { return Color::black; } Color RenderThemeAndroid::platformInactiveSelectionForegroundColor() const { return Color::black; } Color RenderThemeAndroid::platformTextSearchHighlightColor() const { return Color(Color::transparent); } Color RenderThemeAndroid::platformActiveListBoxSelectionBackgroundColor() const { return Color(Color::transparent); } Color RenderThemeAndroid::platformInactiveListBoxSelectionBackgroundColor() const { return Color(Color::transparent); } Color RenderThemeAndroid::platformActiveListBoxSelectionForegroundColor() const { return Color(Color::transparent); } Color RenderThemeAndroid::platformInactiveListBoxSelectionForegroundColor() const { return Color(Color::transparent); } int RenderThemeAndroid::baselinePosition(const RenderObject* obj) const { // From the description of this function in RenderTheme.h: // A method to obtain the baseline position for a "leaf" control. This will only be used if a baseline // position cannot be determined by examining child content. Checkboxes and radio buttons are examples of // controls that need to do this. // // Our checkboxes and radio buttons need to be offset to line up properly. return RenderTheme::baselinePosition(obj) - 2; } void RenderThemeAndroid::addIntrinsicMargins(RenderStyle* style) const { // Cut out the intrinsic margins completely if we end up using a small font size if (style->fontSize() < 11) return; // Intrinsic margin value. const int m = 2; // FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed. if (style->width().isIntrinsicOrAuto()) { if (style->marginLeft().quirk()) style->setMarginLeft(Length(m, Fixed)); if (style->marginRight().quirk()) style->setMarginRight(Length(m, Fixed)); } if (style->height().isAuto()) { if (style->marginTop().quirk()) style->setMarginTop(Length(m, Fixed)); if (style->marginBottom().quirk()) style->setMarginBottom(Length(m, Fixed)); } } bool RenderThemeAndroid::supportsFocus(ControlPart appearance) { switch (appearance) { case PushButtonPart: case ButtonPart: case TextFieldPart: return true; default: return false; } return false; } void RenderThemeAndroid::adjustButtonStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const { // Code is taken from RenderThemeSafari.cpp // It makes sure we have enough space for the button text. const int padding = 8; style->setPaddingLeft(Length(padding, Fixed)); style->setPaddingRight(Length(padding, Fixed)); // Set a min-height so that we can't get smaller than the mini button. style->setMinHeight(Length(15, Fixed)); } bool RenderThemeAndroid::paintCheckbox(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { RenderSkinRadio::Draw(getCanvasFromInfo(info), obj->node(), rect, true); return false; } bool RenderThemeAndroid::paintButton(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { // If it is a disabled button, simply paint it to the master picture. Node* node = obj->node(); Element* formControlElement = static_cast(node); if (formControlElement && !formControlElement->isEnabledFormControl()) RenderSkinButton::Draw(getCanvasFromInfo(info), rect, RenderSkinAndroid::kDisabled); else // Store all the important information in the platform context. info.context->platformContext()->storeButtonInfo(node, rect); // We always return false so we do not request to be redrawn. return false; } #if ENABLE(VIDEO) String RenderThemeAndroid::extraMediaControlsStyleSheet() { return String(mediaControlsAndroidUserAgentStyleSheet, sizeof(mediaControlsAndroidUserAgentStyleSheet)); } bool RenderThemeAndroid::shouldRenderMediaControlPart(ControlPart part, Element* e) { HTMLMediaElement* mediaElement = static_cast(e); switch (part) { case MediaMuteButtonPart: return false; case MediaSeekBackButtonPart: case MediaSeekForwardButtonPart: return true; case MediaRewindButtonPart: return mediaElement->movieLoadType() != MediaPlayer::LiveStream; case MediaReturnToRealtimeButtonPart: return mediaElement->movieLoadType() == MediaPlayer::LiveStream; case MediaFullscreenButtonPart: return mediaElement->supportsFullscreen(); case MediaToggleClosedCaptionsButtonPart: return mediaElement->hasClosedCaptions(); default: return true; } } bool RenderThemeAndroid::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::MUTE); return false; } bool RenderThemeAndroid::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { if (MediaControlPlayButtonElement* btn = static_cast(o->node())) { if (btn->displayType() == MediaPlayButton) RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::PLAY); else RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::PAUSE); return false; } return true; } bool RenderThemeAndroid::paintMediaSeekBackButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::REWIND); return false; } bool RenderThemeAndroid::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::FORWARD); return false; } bool RenderThemeAndroid::paintMediaControlsBackground(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::BACKGROUND_SLIDER); return false; } bool RenderThemeAndroid::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::SLIDER_TRACK); return false; } bool RenderThemeAndroid::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect) { RenderSkinMediaButton::Draw(getCanvasFromInfo(paintInfo), rect, RenderSkinMediaButton::SLIDER_THUMB); return false; } void RenderThemeAndroid::adjustSliderThumbSize(RenderObject* o) const { static const int sliderThumbWidth = RenderSkinMediaButton::sliderThumbWidth(); static const int sliderThumbHeight = RenderSkinMediaButton::sliderThumbHeight(); if (o->style()->appearance() == MediaSliderThumbPart) { o->style()->setWidth(Length(sliderThumbWidth, Fixed)); o->style()->setHeight(Length(sliderThumbHeight, Fixed)); } } #endif bool RenderThemeAndroid::paintRadio(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { RenderSkinRadio::Draw(getCanvasFromInfo(info), obj->node(), rect, false); return false; } void RenderThemeAndroid::setCheckboxSize(RenderStyle* style) const { style->setWidth(Length(19, Fixed)); style->setHeight(Length(19, Fixed)); } void RenderThemeAndroid::setRadioSize(RenderStyle* style) const { // This is the same as checkboxes. setCheckboxSize(style); } void RenderThemeAndroid::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const { addIntrinsicMargins(style); } bool RenderThemeAndroid::paintTextField(RenderObject*, const PaintInfo&, const IntRect&) { return true; } void RenderThemeAndroid::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle* style, WebCore::Element*) const { addIntrinsicMargins(style); } bool RenderThemeAndroid::paintTextArea(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { if (!obj->isListBox()) return true; paintCombo(obj, info, rect); RenderStyle* style = obj->style(); if (style) style->setColor(Color::transparent); Node* node = obj->node(); if (!node || !node->hasTagName(HTMLNames::selectTag)) return true; HTMLSelectElement* select = static_cast(node); // The first item may be visible. Make sure it does not draw. // If it has a style, it overrides the RenderListBox's style, so we // need to make sure both are set to transparent. node = select->item(0); if (node) { RenderObject* renderer = node->renderer(); if (renderer) { RenderStyle* renderStyle = renderer->style(); if (renderStyle) renderStyle->setColor(Color::transparent); } } // Find the first selected option, and draw its text. // FIXME: In a later change, if there is more than one item selected, // draw a string that says "X items" like iPhone Safari does int index = select->selectedIndex(); node = select->item(index); if (!node || !node->hasTagName(HTMLNames::optionTag)) return true; HTMLOptionElement* option = static_cast(node); String label = option->textIndentedToRespectGroupLabel(); SkRect r(rect); SkPaint paint; paint.setAntiAlias(true); paint.setTextEncoding(SkPaint::kUTF16_TextEncoding); // Values for text size and positioning determined by trial and error paint.setTextSize(r.height() - SkIntToScalar(6)); SkCanvas* canvas = getCanvasFromInfo(info); int saveCount = canvas->save(); r.fRight -= SkIntToScalar(RenderSkinCombo::extraWidth()); canvas->clipRect(r); canvas->drawText(label.characters(), label.length() << 1, r.fLeft + SkIntToScalar(5), r.fBottom - SkIntToScalar(5), paint); canvas->restoreToCount(saveCount); return true; } void RenderThemeAndroid::adjustSearchFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const { addIntrinsicMargins(style); } bool RenderThemeAndroid::paintSearchField(RenderObject*, const PaintInfo&, const IntRect&) { return true; } void RenderThemeAndroid::adjustListboxStyle(CSSStyleSelector*, RenderStyle* style, Element*) const { style->setPaddingRight(Length(RenderSkinCombo::extraWidth(), Fixed)); style->setMaxHeight(Length(style->fontSize() + listboxPadding, Fixed)); // Make webkit draw invisible, since it will simply draw the first element style->setColor(Color::transparent); addIntrinsicMargins(style); } static void adjustMenuListStyleCommon(RenderStyle* style, Element* e) { // Added to make room for our arrow and make the touch target less cramped. style->setPaddingLeft(Length(RenderSkinCombo::padding(), Fixed)); style->setPaddingTop(Length(RenderSkinCombo::padding(), Fixed)); style->setPaddingBottom(Length(RenderSkinCombo::padding(), Fixed)); style->setPaddingRight(Length(RenderSkinCombo::extraWidth(), Fixed)); } void RenderThemeAndroid::adjustMenuListStyle(CSSStyleSelector*, RenderStyle* style, Element* e) const { adjustMenuListStyleCommon(style, e); addIntrinsicMargins(style); } bool RenderThemeAndroid::paintCombo(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { if (obj->style() && !obj->style()->visitedDependentColor(CSSPropertyBackgroundColor).alpha()) return true; return RenderSkinCombo::Draw(getCanvasFromInfo(info), obj->node(), rect.x(), rect.y(), rect.width(), rect.height()); } bool RenderThemeAndroid::paintMenuList(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { return paintCombo(obj, info, rect); } void RenderThemeAndroid::adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle* style, Element* e) const { // Copied from RenderThemeSafari. const float baseFontSize = 11.0f; const int baseBorderRadius = 5; float fontScale = style->fontSize() / baseFontSize; style->resetPadding(); style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up? const int minHeight = 15; style->setMinHeight(Length(minHeight, Fixed)); style->setLineHeight(RenderStyle::initialLineHeight()); // Found these padding numbers by trial and error. const int padding = 4; style->setPaddingTop(Length(padding, Fixed)); style->setPaddingLeft(Length(padding, Fixed)); adjustMenuListStyleCommon(style, e); } bool RenderThemeAndroid::paintMenuListButton(RenderObject* obj, const PaintInfo& info, const IntRect& rect) { return paintCombo(obj, info, rect); } bool RenderThemeAndroid::supportsFocusRing(const RenderStyle* style) const { return style->opacity() > 0 && style->hasAppearance() && style->appearance() != TextFieldPart && style->appearance() != SearchFieldPart && style->appearance() != TextAreaPart && style->appearance() != CheckboxPart && style->appearance() != RadioPart && style->appearance() != PushButtonPart && style->appearance() != SquareButtonPart && style->appearance() != ButtonPart && style->appearance() != ButtonBevelPart && style->appearance() != MenulistPart && style->appearance() != MenulistButtonPart; } } // namespace WebCore