You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

612 lines
24 KiB

/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
* Copyright 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "breezedecoration.h"
#include "breeze.h"
#include "breezesettingsprovider.h"
#include "config-breeze.h"
#include "config/breezeconfig.h"
#include "breezebutton.h"
#include "breezesizegrip.h"
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationButtonGroup>
#include <KDecoration2/DecorationSettings>
#include <KDecoration2/DecorationShadow>
#include <KConfigGroup>
#include <KColorUtils>
#include <KSharedConfig>
#include <KPluginFactory>
#include <QPainter>
#include <QTextStream>
#if BREEZE_HAVE_X11
#include <QX11Info>
#endif
K_PLUGIN_FACTORY_WITH_JSON(
BreezeDecoFactory,
"breeze.json",
registerPlugin<Breeze::Decoration>();
registerPlugin<Breeze::Button>(QStringLiteral("button"));
registerPlugin<Breeze::ConfigurationModule>(QStringLiteral("kcmodule"));
)
namespace Breeze
{
//________________________________________________________________
static int g_sDecoCount = 0;
static QSharedPointer<KDecoration2::DecorationShadow> g_sShadow;
Decoration::Decoration(QObject *parent, const QVariantList &args)
: KDecoration2::Decoration(parent, args)
, m_colorSettings(client().data()->palette())
, m_animation( new QPropertyAnimation( this ) )
{
g_sDecoCount++;
m_useSeparator = (m_colorSettings.palette().color( QPalette::Window ) != m_colorSettings.activeTitleBar() );
}
//________________________________________________________________
Decoration::~Decoration()
{
g_sDecoCount--;
if (g_sDecoCount == 0) {
// last deco destroyed, clean up shadow
g_sShadow.clear();
}
}
//________________________________________________________________
void Decoration::setOpacity( qreal value )
{
if( m_opacity == value ) return;
m_opacity = value;
update();
if( m_sizeGrip ) m_sizeGrip->update();
}
//________________________________________________________________
QColor Decoration::titleBarColor() const
{
if( m_animation->state() == QPropertyAnimation::Running )
{
return KColorUtils::mix( m_colorSettings.inactiveTitleBar(), m_colorSettings.activeTitleBar(), m_opacity );
} else return m_colorSettings.titleBar( client().data()->isActive() );
}
//________________________________________________________________
QColor Decoration::outlineColor() const
{
if( !m_useSeparator ) return QColor();
if( m_animation->state() == QPropertyAnimation::Running )
{
QColor color( m_colorSettings.palette().color( QPalette::Highlight ) );
color.setAlpha( color.alpha()*m_opacity );
return color;
} else if( client().data()->isActive() ) return m_colorSettings.palette().color( QPalette::Highlight );
else return QColor();
}
//________________________________________________________________
QColor Decoration::fontColor() const
{
if( m_animation->state() == QPropertyAnimation::Running )
{
return KColorUtils::mix( m_colorSettings.inactiveFont(), m_colorSettings.activeFont(), m_opacity );
} else return m_colorSettings.font( client().data()->isActive() );
}
//________________________________________________________________
void Decoration::init()
{
// active state change animation
m_animation->setStartValue( 0 );
m_animation->setEndValue( 1.0 );
m_animation->setTargetObject( this );
m_animation->setPropertyName( "opacity" );
m_animation->setEasingCurve( QEasingCurve::InOutQuad );
reconfigure();
updateTitleBar();
auto s = settings();
connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, this, &Decoration::recalculateBorders);
// a change in font might cause the borders to change
connect(s.data(), &KDecoration2::DecorationSettings::fontChanged, this, &Decoration::recalculateBorders);
connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::recalculateBorders);
// full reconfiguration
connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::reconfigure);
connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, SettingsProvider::self(), &SettingsProvider::reconfigure, Qt::UniqueConnection );
connect(client().data(), &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::recalculateBorders);
connect(client().data(), &KDecoration2::DecoratedClient::maximizedHorizontallyChanged, this, &Decoration::recalculateBorders);
connect(client().data(), &KDecoration2::DecoratedClient::maximizedVerticallyChanged, this, &Decoration::recalculateBorders);
connect(client().data(), &KDecoration2::DecoratedClient::captionChanged, this,
[this]()
{
// update the caption area
update(titleBar());
}
);
connect(client().data(), &KDecoration2::DecoratedClient::activeChanged, this, &Decoration::updateAnimationState);
connect(client().data(), &KDecoration2::DecoratedClient::paletteChanged, this,
[this]() {
m_colorSettings.update(client().data()->palette());
m_useSeparator = (m_colorSettings.palette().color( QPalette::Window ) != m_colorSettings.activeTitleBar() );
update();
}
);
connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar);
connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar);
connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::setOpaque);
connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateButtonsGeometry);
connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateButtonsGeometry);
connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::recalculateBorders);
connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateButtonsGeometry);
createButtons();
createShadow();
}
//________________________________________________________________
void Decoration::updateTitleBar()
{
auto s = settings();
const bool maximized = isMaximized();
const int width = maximized ? client().data()->width() : client().data()->width() - 2*s->largeSpacing()*Metrics::TitleBar_SideMargin;
const int height = maximized ? borderTop() : borderTop() - s->smallSpacing()*Metrics::TitleBar_TopMargin;
const int x = maximized ? 0 : s->largeSpacing()*Metrics::TitleBar_SideMargin;
const int y = maximized ? 0 : s->smallSpacing()*Metrics::TitleBar_TopMargin;
setTitleBar(QRect(x, y, width, height));
}
//________________________________________________________________
void Decoration::updateAnimationState()
{
if( m_internalSettings->animationsEnabled() )
{
m_animation->setDirection( client().data()->isActive() ? QPropertyAnimation::Forward : QPropertyAnimation::Backward );
if( m_animation->state() != QPropertyAnimation::Running ) m_animation->start();
}
}
//________________________________________________________________
int Decoration::borderSize(bool bottom) const
{
const int baseSize = settings()->smallSpacing();
auto maxSize = [] (int size) {
const int minSize = 4;
return qMax(size, minSize);
};
if( m_internalSettings && (m_internalSettings->mask() & BorderSize ) )
{
switch (m_internalSettings->borderSize()) {
case InternalSettings::BorderNone: return 0;
case InternalSettings::BorderNoSides: return bottom ? maxSize(baseSize) : 0;
default:
case InternalSettings::BorderTiny: return maxSize(baseSize);
case InternalSettings::BorderNormal: return maxSize(baseSize*2);
case InternalSettings::BorderLarge: return maxSize(baseSize * 3);
case InternalSettings::BorderVeryLarge: return maxSize(baseSize * 4);
case InternalSettings::BorderHuge: return maxSize(baseSize * 5);
case InternalSettings::BorderVeryHuge: return maxSize(baseSize * 6);
case InternalSettings::BorderOversized: return maxSize(baseSize * 10);
}
} else {
switch (settings()->borderSize()) {
case KDecoration2::BorderSize::None: return 0;
case KDecoration2::BorderSize::NoSides: return bottom ? maxSize(baseSize ) : 0;
default:
case KDecoration2::BorderSize::Tiny: return maxSize(baseSize);
case KDecoration2::BorderSize::Normal: return maxSize(baseSize*2);
case KDecoration2::BorderSize::Large: return maxSize(baseSize * 3);
case KDecoration2::BorderSize::VeryLarge: return maxSize(baseSize * 4);
case KDecoration2::BorderSize::Huge: return maxSize(baseSize * 5);
case KDecoration2::BorderSize::VeryHuge: return maxSize(baseSize * 6);
case KDecoration2::BorderSize::Oversized: return maxSize(baseSize * 10);
}
}
}
//________________________________________________________________
void Decoration::reconfigure()
{
m_internalSettings = SettingsProvider::self()->internalSettings( this );
// read internal settings
// m_internalSettings->read();
// animation
m_animation->setDuration( m_internalSettings->animationsDuration() );
// borders
recalculateBorders();
// size grip
if( hasNoBorders() && m_internalSettings->drawSizeGrip() ) createSizeGrip();
else deleteSizeGrip();
}
//________________________________________________________________
void Decoration::recalculateBorders()
{
auto s = settings();
const auto c = client().data();
const Qt::Edges edges = c->adjacentScreenEdges();
int left = isMaximizedHorizontally() || edges.testFlag(Qt::LeftEdge) ? 0 : borderSize();
int right = isMaximizedHorizontally() || edges.testFlag(Qt::RightEdge) ? 0 : borderSize();
QFontMetrics fm(s->font());
int top = qMax(fm.boundingRect(c->caption()).height(), buttonHeight() );
// padding below
// extra pixel is used for the active window outline
const int baseSize = settings()->smallSpacing();
top += baseSize*Metrics::TitleBar_BottomMargin + 1;
// padding above
top += baseSize*TitleBar_TopMargin;
int bottom = isMaximizedVertically() || c->isShaded() || edges.testFlag(Qt::BottomEdge) ? 0 : borderSize(true);
setBorders(QMargins(left, top, right, bottom));
// extended sizes
const int extSize = s->largeSpacing();
int extSides = 0;
int extBottom = 0;
if( hasNoBorders() )
{
extSides = extSize;
extBottom = extSize;
} else if( hasNoSideBorders() ) {
extSides = extSize;
}
setResizeOnlyBorders(QMargins(extSides, 0, extSides, extBottom));
}
//________________________________________________________________
void Decoration::createButtons()
{
m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &Button::create);
m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &Button::create);
updateButtonsGeometry();
}
//________________________________________________________________
void Decoration::updateButtonsGeometry()
{
auto s = settings();
// adjust button position
const int bHeight = captionHeight() + (isMaximized() ? s->smallSpacing()*Metrics::TitleBar_TopMargin:0);
const int bWidth = buttonHeight();
const int verticalOffset = (isMaximized() ? s->smallSpacing()*Metrics::TitleBar_TopMargin:0) + (captionHeight()-buttonHeight())/2;
foreach( const QPointer<KDecoration2::DecorationButton>& button, m_leftButtons->buttons() + m_rightButtons->buttons() )
{
button.data()->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth, bHeight ) ) );
static_cast<Button*>( button.data() )->setOffset( QPointF( 0, verticalOffset ) );
}
if( isMaximized() )
{
// add offsets on the side buttons, to preserve padding, but satisfy Fitts law
const int hOffset = s->smallSpacing()*Metrics::TitleBar_SideMargin;
if( !m_leftButtons->buttons().isEmpty() )
{
auto button = static_cast<Button*>( m_leftButtons->buttons().front().data() );
button->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth + hOffset, bHeight ) ) );
button->setFlag( Button::FlagFirstInList );
button->setHorizontalOffset( hOffset );
}
if( !m_rightButtons->buttons().isEmpty() )
{
auto button = static_cast<Button*>( m_rightButtons->buttons().back().data() );
button->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth + hOffset, bHeight ) ) );
button->setFlag( Button::FlagLastInList );
button->setHorizontalOffset( hOffset );
}
}
// adjust buttons position
const int vPadding = isMaximized() ? 0 : s->smallSpacing()*Metrics::TitleBar_TopMargin;
const int hPadding = isMaximized() ? 0 : s->smallSpacing()*Metrics::TitleBar_SideMargin;
m_rightButtons->setSpacing(s->smallSpacing()*Metrics::TitleBar_ButtonSpacing);
m_leftButtons->setSpacing(s->smallSpacing()*Metrics::TitleBar_ButtonSpacing);
m_leftButtons->setPos(QPointF(hPadding, vPadding));
m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - hPadding, vPadding));
}
//________________________________________________________________
void Decoration::paint(QPainter *painter, const QRect &repaintRegion)
{
// TODO: optimize based on repaintRegion
// paint background
if( !client().data()->isShaded() )
{
painter->fillRect(rect(), Qt::transparent);
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::NoPen);
painter->setBrush(m_colorSettings.frame(client().data()->isActive()));
// clip away the top part
painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip);
painter->drawRoundedRect(rect(), Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius);
painter->restore();
}
paintTitleBar(painter, repaintRegion);
}
//________________________________________________________________
void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion)
{
const auto c = client().data();
const QRect titleRect(QPoint(0, 0), QSize(size().width(), borderTop()));
// render a linear gradient on title area
const QColor titleBarColor( this->titleBarColor() );
QLinearGradient gradient( 0, 0, 0, titleRect.height() );
gradient.setColorAt(0.0, titleBarColor.lighter(100.0));
gradient.setColorAt(0.8, titleBarColor);
painter->save();
painter->setBrush(gradient);
painter->setPen(Qt::NoPen);
if (isMaximized())
{
painter->drawRect(titleRect);
} else if( c->isShaded() ) {
painter->drawRoundedRect(titleRect, Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius);
} else {
// we make the rect a little bit larger to be able to clip away the rounded corners on bottom
painter->setClipRect(titleRect, Qt::IntersectClip);
painter->drawRoundedRect(titleRect.adjusted(0, 0, 0, Metrics::Frame_FrameRadius), Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius);
}
auto s = settings();
const QColor outlineColor( this->outlineColor() );
if( !c->isShaded() && outlineColor.isValid() )
{
// outline
painter->setRenderHint( QPainter::Antialiasing, false );
painter->setBrush( Qt::NoBrush );
painter->setPen( outlineColor );
painter->drawLine( titleRect.bottomLeft(), titleRect.bottomRight() );
}
painter->restore();
// draw caption
painter->setFont(s->font());
const QRect cR = captionRect();
const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.width());
painter->setPen(m_colorSettings.font(c->isActive()));
painter->drawText(cR, Qt::AlignVCenter| Qt::AlignLeft | Qt::TextSingleLine, caption);
// draw all buttons
m_leftButtons->paint(painter, repaintRegion);
m_rightButtons->paint(painter, repaintRegion);
}
//________________________________________________________________
int Decoration::buttonHeight() const
{
const int baseSize = settings()->gridUnit();
switch( m_internalSettings->buttonSize() )
{
case Breeze::InternalSettings::ButtonSmall: return baseSize*1.5;
default:
case Breeze::InternalSettings::ButtonDefault: return baseSize*2;
case Breeze::InternalSettings::ButtonLarge: return baseSize*2.5;
case Breeze::InternalSettings::ButtonVeryLarge: return baseSize*3.5;
}
}
//________________________________________________________________
int Decoration::captionHeight() const
{ return borderTop() - settings()->smallSpacing()*(Metrics::TitleBar_BottomMargin + Metrics::TitleBar_TopMargin ) - 1; }
//________________________________________________________________
QRect Decoration::captionRect() const
{
const int leftOffset = m_leftButtons->geometry().x() + m_leftButtons->geometry().width() + Metrics::TitleBar_SideMargin*settings()->smallSpacing();
const int rightOffset = size().width() - m_rightButtons->geometry().x() + Metrics::TitleBar_SideMargin*settings()->smallSpacing();
const int yOffset = settings()->smallSpacing()*Metrics::TitleBar_TopMargin;
QRect boundingRect( settings()->fontMetrics().boundingRect( client().data()->caption()).toRect() );
boundingRect.setTop( yOffset );
boundingRect.setHeight( captionHeight() );
/* need to increase the bounding rect because it is sometime (font dependent)
too small, resulting in text being elided */
boundingRect.setWidth( boundingRect.width()+4 );
switch( m_internalSettings->titleAlignment() )
{
case Breeze::InternalSettings::AlignLeft:
boundingRect.moveLeft( leftOffset );
break;
case Breeze::InternalSettings::AlignRight:
boundingRect.moveRight( size().width() - rightOffset - 1 );
break;
case Breeze::InternalSettings::AlignCenter:
boundingRect.moveLeft( leftOffset + (size().width() - leftOffset - rightOffset - boundingRect.width() )/2 );
break;
default:
case Breeze::InternalSettings::AlignCenterFullWidth:
boundingRect.moveLeft( ( size().width() - boundingRect.width() )/2 );
break;
}
// make sure there is no overlap with buttons
if( boundingRect.left() < leftOffset )
{
boundingRect.moveLeft( leftOffset );
boundingRect.setRight( qMin( boundingRect.right(), size().width() - rightOffset - 1 ) );
} else if( boundingRect.right() > size().width() - rightOffset - 1 ) {
boundingRect.moveRight( size().width() - rightOffset - 1 );
boundingRect.setLeft( qMax( boundingRect.left(), leftOffset ) );
}
return boundingRect;
}
//________________________________________________________________
void Decoration::createShadow()
{
if (g_sShadow) {
setShadow(g_sShadow);
return;
}
// setup shadow
auto decorationShadow = QSharedPointer<KDecoration2::DecorationShadow>::create();
decorationShadow->setPadding( QMargins(
Metrics::Shadow_Size-Metrics::Shadow_Offset,
Metrics::Shadow_Size-Metrics::Shadow_Offset,
Metrics::Shadow_Size,
Metrics::Shadow_Size ) );
decorationShadow->setInnerShadowRect(QRect(
Metrics::Shadow_Size-Metrics::Shadow_Offset+Metrics::Shadow_Overlap,
Metrics::Shadow_Size-Metrics::Shadow_Offset+Metrics::Shadow_Overlap,
Metrics::Shadow_Offset - 2*Metrics::Shadow_Overlap,
Metrics::Shadow_Offset - 2*Metrics::Shadow_Overlap ) );
// create image
QImage image(2*Metrics::Shadow_Size, 2*Metrics::Shadow_Size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_Source);
// create gradient
auto gradientStopColor = [](QColor color, qreal alpha) {
color.setAlphaF(alpha);
return color;
};
const QColor shadowColor( m_colorSettings.palette().color( QPalette::Shadow ) );
QRadialGradient radialGradient( Metrics::Shadow_Size, Metrics::Shadow_Size, Metrics::Shadow_Size);
radialGradient.setColorAt(0.0, gradientStopColor(shadowColor, 0.35));
radialGradient.setColorAt(0.25, gradientStopColor(shadowColor, 0.25));
radialGradient.setColorAt(0.5, gradientStopColor(shadowColor, 0.13));
radialGradient.setColorAt(0.75, gradientStopColor(shadowColor, 0.04));
radialGradient.setColorAt(1.0, gradientStopColor(shadowColor, 0.0));
// fill
p.fillRect( image.rect(), radialGradient);
// assign to shadow
decorationShadow->setShadow(image);
g_sShadow = decorationShadow;
setShadow(decorationShadow);
}
//_________________________________________________________________
void Decoration::createSizeGrip( void )
{
// do nothing if size grip already exist
if( m_sizeGrip ) return;
#if BREEZE_HAVE_X11
if( !QX11Info::isPlatformX11() ) return;
// access client
KDecoration2::DecoratedClient *c( client().data() );
if( !c ) return;
if( ( c->isResizeable() && c->windowId() != 0 ) )
{
m_sizeGrip = new SizeGrip( this );
m_sizeGrip->setVisible( !( isMaximized() || c->isShaded() ) );
}
#endif
}
//_________________________________________________________________
void Decoration::deleteSizeGrip( void )
{
if( m_sizeGrip )
{
m_sizeGrip->deleteLater();
m_sizeGrip = nullptr;
}
}
} // namespace
#include "breezedecoration.moc"