blob: ee3ebe08b132742cb02f857f1105a90dce60b2cf [file] [log] [blame]
/*
* Copyright (C) 2015, 2020 Igalia S.L.
*
* 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "ThemeAdwaita.h"
#include "Color.h"
#include "ControlStates.h"
#include "FloatRoundedRect.h"
#include "GraphicsContext.h"
#include "LengthSize.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
static const unsigned focusLineWidth = 1;
static const Color focusRingColor = makeRGBA(46, 52, 54, 150);
static const unsigned arrowSize = 16;
static const Color arrowColor = makeRGB(46, 52, 54);
static const int buttonFocusOffset = -3;
static const unsigned buttonPadding = 5;
static const int buttonBorderSize = 1; // Keep in sync with menuListButtonBorderSize in RenderThemeAdwaita.
static const Color buttonBorderColor = makeRGB(205, 199, 194);
static const Color buttonBackgroundColor = makeRGB(244, 242, 241);
static const Color buttonBackgroundPressedColor = makeRGB(214, 209, 205);
static const Color buttonBackgroundHoveredColor = makeRGB(248, 248, 247);
static const Color buttonBackgroundDisabledColor = makeRGB(246, 246, 244);
static const Color toggleBackgroundColor = makeRGB(255, 255, 255);
static const Color toggleBackgroundHoveredColor = makeRGB(242, 242, 242);
static const Color toggleBackgroundDisabledColor = makeRGB(252, 252, 252);
static const double toggleSize = 14.;
static const int toggleFocusOffset = 2;
static const Color toggleColor = makeRGB(46, 52, 54);
static const Color toggleDisabledColor = makeRGB(160, 160, 160);
static const Color spinButtonBorderColor = makeRGB(220, 223, 227);
static const Color spinButtonBackgroundColor = makeRGB(252, 252, 252);
static const Color spinButtonBackgroundHoveredColor = makeRGBA(46, 52, 54, 50);
static const Color spinButtonBackgroundPressedColor = makeRGBA(46, 52, 54, 70);
#if !PLATFORM(GTK)
Theme& Theme::singleton()
{
static NeverDestroyed<ThemeAdwaita> theme;
return theme;
}
#endif
Color ThemeAdwaita::activeSelectionForegroundColor() const
{
return makeRGB(255, 255, 255);
}
Color ThemeAdwaita::activeSelectionBackgroundColor() const
{
return makeRGB(52, 132, 228);
}
Color ThemeAdwaita::inactiveSelectionForegroundColor() const
{
return makeRGB(252, 252, 252);
}
Color ThemeAdwaita::inactiveSelectionBackgroundColor() const
{
return activeSelectionBackgroundColor();
}
Color ThemeAdwaita::focusColor()
{
return focusRingColor;
}
void ThemeAdwaita::paintFocus(GraphicsContext& graphicsContext, const FloatRect& rect, int offset)
{
FloatRect focusRect = rect;
focusRect.inflate(offset);
Path path;
path.addRoundedRect(focusRect, { 2, 2 });
paintFocus(graphicsContext, path, focusRingColor);
}
void ThemeAdwaita::paintFocus(GraphicsContext& graphicsContext, const Path& path, const Color& color)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
graphicsContext.beginTransparencyLayer(color.alphaAsFloat());
graphicsContext.setStrokeThickness(focusLineWidth);
graphicsContext.setLineDash({ focusLineWidth, 2 * focusLineWidth }, 0);
graphicsContext.setLineCap(SquareCap);
graphicsContext.setLineJoin(MiterJoin);
graphicsContext.setStrokeColor(color.opaqueColor());
graphicsContext.strokePath(path);
graphicsContext.setFillRule(WindRule::NonZero);
graphicsContext.setCompositeOperation(CompositeOperator::Clear);
graphicsContext.fillPath(path);
graphicsContext.setCompositeOperation(CompositeOperator::SourceOver);
graphicsContext.endTransparencyLayer();
}
void ThemeAdwaita::paintFocus(GraphicsContext& graphicsContext, const Vector<FloatRect>& rects, const Color& color)
{
FloatSize corner(2, 2);
Path path;
for (const auto& rect : rects)
path.addRoundedRect(rect, corner);
paintFocus(graphicsContext, path, color);
}
void ThemeAdwaita::paintArrow(GraphicsContext& graphicsContext, ArrowDirection direction)
{
Path path;
switch (direction) {
case ArrowDirection::Down:
path.moveTo({ 3, 6 });
path.addLineTo({ 13, 6 });
path.addLineTo({ 8, 11 });
break;
case ArrowDirection::Up:
path.moveTo({ 3, 10 });
path.addLineTo({ 8, 5 });
path.addLineTo({ 13, 10});
break;
}
path.closeSubpath();
graphicsContext.setFillColor(arrowColor);
graphicsContext.fillPath(path);
}
LengthSize ThemeAdwaita::controlSize(ControlPart part, const FontCascade& fontCascade, const LengthSize& zoomedSize, float zoomFactor) const
{
if (!zoomedSize.width.isIntrinsicOrAuto() && !zoomedSize.height.isIntrinsicOrAuto())
return Theme::controlSize(part, fontCascade, zoomedSize, zoomFactor);
switch (part) {
case CheckboxPart:
case RadioPart: {
LengthSize buttonSize = zoomedSize;
if (buttonSize.width.isIntrinsicOrAuto())
buttonSize.width = Length(12, Fixed);
if (buttonSize.height.isIntrinsicOrAuto())
buttonSize.height = Length(12, Fixed);
return buttonSize;
}
case InnerSpinButtonPart: {
LengthSize spinButtonSize = zoomedSize;
if (spinButtonSize.width.isIntrinsicOrAuto())
spinButtonSize.width = Length(static_cast<int>(arrowSize), Fixed);
if (spinButtonSize.height.isIntrinsicOrAuto() || fontCascade.pixelSize() > static_cast<int>(arrowSize))
spinButtonSize.height = Length(fontCascade.pixelSize(), Fixed);
return spinButtonSize;
}
default:
break;
}
return Theme::controlSize(part, fontCascade, zoomedSize, zoomFactor);
}
LengthSize ThemeAdwaita::minimumControlSize(ControlPart, const FontCascade&, const LengthSize& zoomedSize, float) const
{
if (!zoomedSize.width.isIntrinsicOrAuto() && !zoomedSize.height.isIntrinsicOrAuto())
return zoomedSize;
LengthSize minSize = zoomedSize;
if (minSize.width.isIntrinsicOrAuto())
minSize.width = Length(0, Fixed);
if (minSize.height.isIntrinsicOrAuto())
minSize.height = Length(0, Fixed);
return minSize;
}
LengthBox ThemeAdwaita::controlBorder(ControlPart part, const FontCascade& font, const LengthBox& zoomedBox, float zoomFactor) const
{
switch (part) {
case PushButtonPart:
case DefaultButtonPart:
case ButtonPart:
case SquareButtonPart:
return zoomedBox;
default:
break;
}
return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
}
void ThemeAdwaita::paint(ControlPart part, ControlStates& states, GraphicsContext& context, const FloatRect& zoomedRect, float zoomFactor, ScrollView*, float, float, bool, bool)
{
switch (part) {
case CheckboxPart:
paintCheckbox(states, context, zoomedRect, zoomFactor);
break;
case RadioPart:
paintRadio(states, context, zoomedRect, zoomFactor);
break;
case PushButtonPart:
case DefaultButtonPart:
case ButtonPart:
case SquareButtonPart:
paintButton(states, context, zoomedRect, zoomFactor);
break;
case InnerSpinButtonPart:
paintSpinButton(states, context, zoomedRect, zoomFactor);
break;
default:
break;
}
}
void ThemeAdwaita::paintCheckbox(ControlStates& states, GraphicsContext& graphicsContext, const FloatRect& zoomedRect, float)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
FloatRect fieldRect = zoomedRect;
if (fieldRect.width() != fieldRect.height()) {
auto buttonSize = std::min(fieldRect.width(), fieldRect.height());
fieldRect.setSize({ buttonSize, buttonSize });
if (fieldRect.width() != zoomedRect.width())
fieldRect.move((zoomedRect.width() - fieldRect.width()) / 2.0, 0);
else
fieldRect.move(0, (zoomedRect.height() - fieldRect.height()) / 2.0);
}
FloatSize corner(2, 2);
Path path;
path.addRoundedRect(fieldRect, corner);
fieldRect.inflate(-buttonBorderSize);
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::EvenOdd);
graphicsContext.setFillColor(buttonBorderColor);
graphicsContext.fillPath(path);
path.clear();
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::NonZero);
if (!(states.states() & ControlStates::EnabledState))
graphicsContext.setFillColor(toggleBackgroundDisabledColor);
else if (states.states() & ControlStates::HoverState)
graphicsContext.setFillColor(toggleBackgroundHoveredColor);
else
graphicsContext.setFillColor(toggleBackgroundColor);
graphicsContext.fillPath(path);
path.clear();
if (states.states() & (ControlStates::CheckedState | ControlStates::IndeterminateState)) {
GraphicsContextStateSaver checkedStateSaver(graphicsContext);
graphicsContext.translate(fieldRect.x(), fieldRect.y());
graphicsContext.scale(FloatSize::narrowPrecision(fieldRect.width() / toggleSize, fieldRect.height() / toggleSize));
if (states.states() & ControlStates::CheckedState) {
path.moveTo({ 2.43, 6.57 });
path.addLineTo({ 7.5, 11.63 });
path.addLineTo({ 14, 5 });
path.addLineTo({ 14, 1 });
path.addLineTo({ 7.5, 7.38 });
path.addLineTo({ 4.56, 4.44 });
path.closeSubpath();
} else
path.addRoundedRect(FloatRect(2, 5, 10, 4), corner);
if (!(states.states() & ControlStates::EnabledState))
graphicsContext.setFillColor(toggleDisabledColor);
else
graphicsContext.setFillColor(toggleColor);
graphicsContext.fillPath(path);
path.clear();
}
if (states.states() & ControlStates::FocusState)
paintFocus(graphicsContext, zoomedRect, toggleFocusOffset);
}
void ThemeAdwaita::paintRadio(ControlStates& states, GraphicsContext& graphicsContext, const FloatRect& zoomedRect, float)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
FloatRect fieldRect = zoomedRect;
if (fieldRect.width() != fieldRect.height()) {
auto buttonSize = std::min(fieldRect.width(), fieldRect.height());
fieldRect.setSize({ buttonSize, buttonSize });
if (fieldRect.width() != zoomedRect.width())
fieldRect.move((zoomedRect.width() - fieldRect.width()) / 2.0, 0);
else
fieldRect.move(0, (zoomedRect.height() - fieldRect.height()) / 2.0);
}
Path path;
path.addEllipse(fieldRect);
fieldRect.inflate(-buttonBorderSize);
path.addEllipse(fieldRect);
graphicsContext.setFillRule(WindRule::EvenOdd);
graphicsContext.setFillColor(buttonBorderColor);
graphicsContext.fillPath(path);
path.clear();
path.addEllipse(fieldRect);
graphicsContext.setFillRule(WindRule::NonZero);
if (!(states.states() & ControlStates::EnabledState))
graphicsContext.setFillColor(toggleBackgroundDisabledColor);
else if (states.states() & ControlStates::HoverState)
graphicsContext.setFillColor(toggleBackgroundHoveredColor);
else
graphicsContext.setFillColor(toggleBackgroundColor);
graphicsContext.fillPath(path);
path.clear();
if (states.states() & ControlStates::CheckedState) {
fieldRect.inflate(-(fieldRect.width() - fieldRect.width() * 0.70));
path.addEllipse(fieldRect);
if (!(states.states() & ControlStates::EnabledState))
graphicsContext.setFillColor(toggleDisabledColor);
else
graphicsContext.setFillColor(toggleColor);
graphicsContext.fillPath(path);
}
if (states.states() & ControlStates::FocusState)
paintFocus(graphicsContext, zoomedRect, toggleFocusOffset);
}
void ThemeAdwaita::paintButton(ControlStates& states, GraphicsContext& graphicsContext, const FloatRect& zoomedRect, float)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
FloatRect fieldRect = zoomedRect;
FloatSize corner(5, 5);
Path path;
path.addRoundedRect(fieldRect, corner);
fieldRect.inflate(-buttonBorderSize);
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::EvenOdd);
graphicsContext.setFillColor(buttonBorderColor);
graphicsContext.fillPath(path);
path.clear();
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::NonZero);
if (!(states.states() & ControlStates::EnabledState))
graphicsContext.setFillColor(buttonBackgroundDisabledColor);
else if (states.states() & ControlStates::PressedState)
graphicsContext.setFillColor(buttonBackgroundPressedColor);
else if (states.states() & ControlStates::HoverState)
graphicsContext.setFillColor(buttonBackgroundHoveredColor);
else
graphicsContext.setFillColor(buttonBackgroundColor);
graphicsContext.fillPath(path);
if (states.states() & ControlStates::FocusState)
paintFocus(graphicsContext, zoomedRect, buttonFocusOffset);
}
void ThemeAdwaita::paintSpinButton(ControlStates& states, GraphicsContext& graphicsContext, const FloatRect& zoomedRect, float)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
FloatRect fieldRect = zoomedRect;
FloatSize corner(2, 2);
Path path;
path.addRoundedRect(fieldRect, corner);
fieldRect.inflate(-buttonBorderSize);
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::EvenOdd);
graphicsContext.setFillColor(spinButtonBorderColor);
graphicsContext.fillPath(path);
path.clear();
path.addRoundedRect(fieldRect, corner);
graphicsContext.setFillRule(WindRule::NonZero);
graphicsContext.setFillColor(spinButtonBackgroundColor);
graphicsContext.fillPath(path);
path.clear();
FloatRect buttonRect = fieldRect;
buttonRect.setHeight(fieldRect.height() / 2.0);
{
if (states.states() & ControlStates::SpinUpState) {
path.addRoundedRect(FloatRoundedRect(buttonRect, corner, corner, { }, { }));
if (states.states() & ControlStates::PressedState)
graphicsContext.setFillColor(spinButtonBackgroundPressedColor);
else if (states.states() & ControlStates::HoverState)
graphicsContext.setFillColor(spinButtonBackgroundHoveredColor);
graphicsContext.fillPath(path);
path.clear();
}
GraphicsContextStateSaver buttonStateSaver(graphicsContext);
if (buttonRect.height() > arrowSize)
graphicsContext.translate(buttonRect.x(), buttonRect.y() + (buttonRect.height() / 2.0) - (arrowSize / 2.));
else {
graphicsContext.translate(buttonRect.x(), buttonRect.y());
graphicsContext.scale(FloatSize::narrowPrecision(buttonRect.width() / arrowSize, buttonRect.height() / arrowSize));
}
paintArrow(graphicsContext, ArrowDirection::Up);
}
buttonRect.move(0, buttonRect.height());
{
if (!(states.states() & ControlStates::SpinUpState)) {
path.addRoundedRect(FloatRoundedRect(buttonRect, { }, { }, corner, corner));
if (states.states() & ControlStates::PressedState)
graphicsContext.setFillColor(spinButtonBackgroundPressedColor);
else if (states.states() & ControlStates::HoverState)
graphicsContext.setFillColor(spinButtonBackgroundHoveredColor);
graphicsContext.fillPath(path);
path.clear();
}
GraphicsContextStateSaver buttonStateSaver(graphicsContext);
if (buttonRect.height() > arrowSize)
graphicsContext.translate(buttonRect.x(), buttonRect.y() + (buttonRect.height() / 2.0) - (arrowSize / 2.));
else {
graphicsContext.translate(buttonRect.x(), buttonRect.y());
graphicsContext.scale(FloatSize::narrowPrecision(buttonRect.width() / arrowSize, buttonRect.height() / arrowSize));
}
paintArrow(graphicsContext, ArrowDirection::Down);
}
}
} // namespace WebCore