blob: e6ab416d168fdeedcc2f936b5fe317147a4d9ff7 [file] [log] [blame]
/**
* This file is part of the theme implementation for form controls in WebCore.
*
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/paint/ThemePainter.h"
#include "core/InputTypeNames.h"
#include "core/frame/FrameView.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLDataListElement.h"
#include "core/html/HTMLDataListOptionsCollection.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLOptionElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/LayoutView.h"
#include "core/paint/MediaControlsPainter.h"
#include "core/paint/PaintInfo.h"
#include "core/style/ComputedStyle.h"
#include "platform/Theme.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "public/platform/Platform.h"
#include "public/platform/WebFallbackThemeEngine.h"
#include "public/platform/WebRect.h"
// The methods in this file are shared by all themes on every platform.
namespace blink {
static WebFallbackThemeEngine::State getWebFallbackThemeState(
const LayoutObject& o) {
if (!LayoutTheme::isEnabled(o))
return WebFallbackThemeEngine::StateDisabled;
if (LayoutTheme::isPressed(o))
return WebFallbackThemeEngine::StatePressed;
if (LayoutTheme::isHovered(o))
return WebFallbackThemeEngine::StateHover;
return WebFallbackThemeEngine::StateNormal;
}
ThemePainter::ThemePainter() {}
bool ThemePainter::paint(const LayoutObject& o,
const PaintInfo& paintInfo,
const IntRect& r) {
ControlPart part = o.styleRef().appearance();
if (LayoutTheme::theme().shouldUseFallbackTheme(o.styleRef()))
return paintUsingFallbackTheme(o, paintInfo, r);
if (part == ButtonPart && o.node()) {
UseCounter::count(o.document(),
UseCounter::CSSValueAppearanceButtonRendered);
if (isHTMLAnchorElement(o.node())) {
UseCounter::count(o.document(),
UseCounter::CSSValueAppearanceButtonForAnchor);
} else if (isHTMLButtonElement(o.node())) {
UseCounter::count(o.document(),
UseCounter::CSSValueAppearanceButtonForButton);
} else if (isHTMLInputElement(o.node()) &&
toHTMLInputElement(o.node())->isTextButton()) {
// Text buttons (type=button, reset, submit) has
// -webkit-appearance:push-button by default.
UseCounter::count(o.node()->document(),
UseCounter::CSSValueAppearanceButtonForOtherButtons);
}
}
// Call the appropriate paint method based off the appearance value.
switch (part) {
case CheckboxPart:
return paintCheckbox(o, paintInfo, r);
case RadioPart:
return paintRadio(o, paintInfo, r);
case PushButtonPart:
case SquareButtonPart:
case ButtonPart:
return paintButton(o, paintInfo, r);
case InnerSpinButtonPart:
return paintInnerSpinButton(o, paintInfo, r);
case MenulistPart:
return paintMenuList(o, paintInfo, r);
case MeterPart:
return true;
case ProgressBarPart:
return paintProgressBar(o, paintInfo, r);
case SliderHorizontalPart:
case SliderVerticalPart:
return paintSliderTrack(o, paintInfo, r);
case SliderThumbHorizontalPart:
case SliderThumbVerticalPart:
return paintSliderThumb(o, paintInfo, r);
case MediaEnterFullscreenButtonPart:
case MediaExitFullscreenButtonPart:
return MediaControlsPainter::paintMediaFullscreenButton(o, paintInfo, r);
case MediaPlayButtonPart:
return MediaControlsPainter::paintMediaPlayButton(o, paintInfo, r);
case MediaOverlayPlayButtonPart:
return MediaControlsPainter::paintMediaOverlayPlayButton(o, paintInfo, r);
case MediaMuteButtonPart:
return MediaControlsPainter::paintMediaMuteButton(o, paintInfo, r);
case MediaToggleClosedCaptionsButtonPart:
return MediaControlsPainter::paintMediaToggleClosedCaptionsButton(
o, paintInfo, r);
case MediaSliderPart:
return MediaControlsPainter::paintMediaSlider(o, paintInfo, r);
case MediaSliderThumbPart:
return MediaControlsPainter::paintMediaSliderThumb(o, paintInfo, r);
case MediaVolumeSliderContainerPart:
return true;
case MediaVolumeSliderPart:
return MediaControlsPainter::paintMediaVolumeSlider(o, paintInfo, r);
case MediaVolumeSliderThumbPart:
return MediaControlsPainter::paintMediaVolumeSliderThumb(o, paintInfo, r);
case MediaFullscreenVolumeSliderPart:
case MediaFullscreenVolumeSliderThumbPart:
case MediaTimeRemainingPart:
case MediaCurrentTimePart:
case MediaControlsBackgroundPart:
return true;
case MediaCastOffButtonPart:
case MediaOverlayCastOffButtonPart:
return MediaControlsPainter::paintMediaCastButton(o, paintInfo, r);
case MediaTrackSelectionCheckmarkPart:
return MediaControlsPainter::paintMediaTrackSelectionCheckmark(
o, paintInfo, r);
case MediaClosedCaptionsIconPart:
return MediaControlsPainter::paintMediaClosedCaptionsIcon(o, paintInfo,
r);
case MediaSubtitlesIconPart:
return MediaControlsPainter::paintMediaSubtitlesIcon(o, paintInfo, r);
case MediaOverflowMenuButtonPart:
return MediaControlsPainter::paintMediaOverflowMenu(o, paintInfo, r);
case MediaDownloadIconPart:
return MediaControlsPainter::paintMediaDownloadIcon(o, paintInfo, r);
case MenulistButtonPart:
case TextFieldPart:
case TextAreaPart:
return true;
case SearchFieldPart:
return paintSearchField(o, paintInfo, r);
case SearchFieldCancelButtonPart:
return paintSearchFieldCancelButton(o, paintInfo, r);
default:
break;
}
return true; // We don't support the appearance, so let the normal
// background/border paint.
}
bool ThemePainter::paintBorderOnly(const LayoutObject& o,
const PaintInfo& paintInfo,
const IntRect& r) {
// Call the appropriate paint method based off the appearance value.
switch (o.styleRef().appearance()) {
case TextFieldPart:
UseCounter::count(o.document(),
UseCounter::CSSValueAppearanceTextFieldRendered);
if (isHTMLInputElement(o.node())) {
HTMLInputElement* input = toHTMLInputElement(o.node());
if (input->type() == InputTypeNames::search)
UseCounter::count(o.document(),
UseCounter::CSSValueAppearanceTextFieldForSearch);
else if (input->isTextField())
UseCounter::count(
o.document(),
UseCounter::CSSValueAppearanceTextFieldForTextField);
}
return paintTextField(o, paintInfo, r);
case TextAreaPart:
return paintTextArea(o, paintInfo, r);
case MenulistButtonPart:
case SearchFieldPart:
case ListboxPart:
return true;
case CheckboxPart:
case RadioPart:
case PushButtonPart:
case SquareButtonPart:
case ButtonPart:
case MenulistPart:
case MeterPart:
case ProgressBarPart:
case SliderHorizontalPart:
case SliderVerticalPart:
case SliderThumbHorizontalPart:
case SliderThumbVerticalPart:
case SearchFieldCancelButtonPart:
default:
break;
}
return false;
}
bool ThemePainter::paintDecorations(const LayoutObject& o,
const PaintInfo& paintInfo,
const IntRect& r) {
// Call the appropriate paint method based off the appearance value.
switch (o.styleRef().appearance()) {
case MenulistButtonPart:
return paintMenuListButton(o, paintInfo, r);
case TextFieldPart:
case TextAreaPart:
case CheckboxPart:
case RadioPart:
case PushButtonPart:
case SquareButtonPart:
case ButtonPart:
case MenulistPart:
case MeterPart:
case ProgressBarPart:
case SliderHorizontalPart:
case SliderVerticalPart:
case SliderThumbHorizontalPart:
case SliderThumbVerticalPart:
case SearchFieldPart:
case SearchFieldCancelButtonPart:
default:
break;
}
return false;
}
void ThemePainter::paintSliderTicks(const LayoutObject& o,
const PaintInfo& paintInfo,
const IntRect& rect) {
Node* node = o.node();
if (!isHTMLInputElement(node))
return;
HTMLInputElement* input = toHTMLInputElement(node);
if (input->type() != InputTypeNames::range ||
!input->userAgentShadowRoot()->hasChildren())
return;
HTMLDataListElement* dataList = input->dataList();
if (!dataList)
return;
double min = input->minimum();
double max = input->maximum();
ControlPart part = o.styleRef().appearance();
// We don't support ticks on alternate sliders like MediaVolumeSliders.
if (part != SliderHorizontalPart && part != SliderVerticalPart)
return;
bool isHorizontal = part == SliderHorizontalPart;
IntSize thumbSize;
LayoutObject* thumbLayoutObject =
input->userAgentShadowRoot()
->getElementById(ShadowElementNames::sliderThumb())
->layoutObject();
if (thumbLayoutObject) {
const ComputedStyle& thumbStyle = thumbLayoutObject->styleRef();
int thumbWidth = thumbStyle.width().intValue();
int thumbHeight = thumbStyle.height().intValue();
thumbSize.setWidth(isHorizontal ? thumbWidth : thumbHeight);
thumbSize.setHeight(isHorizontal ? thumbHeight : thumbWidth);
}
IntSize tickSize = LayoutTheme::theme().sliderTickSize();
float zoomFactor = o.styleRef().effectiveZoom();
FloatRect tickRect;
int tickRegionSideMargin = 0;
int tickRegionWidth = 0;
IntRect trackBounds;
LayoutObject* trackLayoutObject =
input->userAgentShadowRoot()
->getElementById(ShadowElementNames::sliderTrack())
->layoutObject();
// We can ignoring transforms because transform is handled by the graphics
// context.
if (trackLayoutObject)
trackBounds =
trackLayoutObject->absoluteBoundingBoxRectIgnoringTransforms();
IntRect sliderBounds = o.absoluteBoundingBoxRectIgnoringTransforms();
// Make position relative to the transformed ancestor element.
trackBounds.setX(trackBounds.x() - sliderBounds.x() + rect.x());
trackBounds.setY(trackBounds.y() - sliderBounds.y() + rect.y());
if (isHorizontal) {
tickRect.setWidth(floor(tickSize.width() * zoomFactor));
tickRect.setHeight(floor(tickSize.height() * zoomFactor));
tickRect.setY(floor(rect.y() + rect.height() / 2.0 +
LayoutTheme::theme().sliderTickOffsetFromTrackCenter() *
zoomFactor));
tickRegionSideMargin =
trackBounds.x() +
(thumbSize.width() - tickSize.width() * zoomFactor) / 2.0;
tickRegionWidth = trackBounds.width() - thumbSize.width();
} else {
tickRect.setWidth(floor(tickSize.height() * zoomFactor));
tickRect.setHeight(floor(tickSize.width() * zoomFactor));
tickRect.setX(floor(rect.x() + rect.width() / 2.0 +
LayoutTheme::theme().sliderTickOffsetFromTrackCenter() *
zoomFactor));
tickRegionSideMargin =
trackBounds.y() +
(thumbSize.width() - tickSize.width() * zoomFactor) / 2.0;
tickRegionWidth = trackBounds.height() - thumbSize.width();
}
HTMLDataListOptionsCollection* options = dataList->options();
for (unsigned i = 0; HTMLOptionElement* optionElement = options->item(i);
i++) {
String value = optionElement->value();
if (!input->isValidValue(value))
continue;
double parsedValue =
parseToDoubleForNumberType(input->sanitizeValue(value));
double tickFraction = (parsedValue - min) / (max - min);
double tickRatio = isHorizontal && o.styleRef().isLeftToRightDirection()
? tickFraction
: 1.0 - tickFraction;
double tickPosition =
round(tickRegionSideMargin + tickRegionWidth * tickRatio);
if (isHorizontal)
tickRect.setX(tickPosition);
else
tickRect.setY(tickPosition);
paintInfo.context.fillRect(tickRect, o.resolveColor(CSSPropertyColor));
}
}
bool ThemePainter::paintUsingFallbackTheme(const LayoutObject& o,
const PaintInfo& i,
const IntRect& r) {
ControlPart part = o.styleRef().appearance();
switch (part) {
case CheckboxPart:
return paintCheckboxUsingFallbackTheme(o, i, r);
case RadioPart:
return paintRadioUsingFallbackTheme(o, i, r);
default:
break;
}
return true;
}
bool ThemePainter::paintCheckboxUsingFallbackTheme(const LayoutObject& o,
const PaintInfo& i,
const IntRect& r) {
WebFallbackThemeEngine::ExtraParams extraParams;
WebCanvas* canvas = i.context.canvas();
extraParams.button.checked = LayoutTheme::isChecked(o);
extraParams.button.indeterminate = LayoutTheme::isIndeterminate(o);
float zoomLevel = o.styleRef().effectiveZoom();
GraphicsContextStateSaver stateSaver(i.context);
IntRect unzoomedRect = r;
if (zoomLevel != 1) {
unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
i.context.translate(unzoomedRect.x(), unzoomedRect.y());
i.context.scale(zoomLevel, zoomLevel);
i.context.translate(-unzoomedRect.x(), -unzoomedRect.y());
}
Platform::current()->fallbackThemeEngine()->paint(
canvas, WebFallbackThemeEngine::PartCheckbox, getWebFallbackThemeState(o),
WebRect(unzoomedRect), &extraParams);
return false;
}
bool ThemePainter::paintRadioUsingFallbackTheme(const LayoutObject& o,
const PaintInfo& i,
const IntRect& r) {
WebFallbackThemeEngine::ExtraParams extraParams;
WebCanvas* canvas = i.context.canvas();
extraParams.button.checked = LayoutTheme::isChecked(o);
extraParams.button.indeterminate = LayoutTheme::isIndeterminate(o);
float zoomLevel = o.styleRef().effectiveZoom();
GraphicsContextStateSaver stateSaver(i.context);
IntRect unzoomedRect = r;
if (zoomLevel != 1) {
unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
i.context.translate(unzoomedRect.x(), unzoomedRect.y());
i.context.scale(zoomLevel, zoomLevel);
i.context.translate(-unzoomedRect.x(), -unzoomedRect.y());
}
Platform::current()->fallbackThemeEngine()->paint(
canvas, WebFallbackThemeEngine::PartRadio, getWebFallbackThemeState(o),
WebRect(unzoomedRect), &extraParams);
return false;
}
} // namespace blink