diff --git a/src/controls/Theme.qml b/src/controls/Theme.qml index 8ef8054d..64596d5a 100644 --- a/src/controls/Theme.qml +++ b/src/controls/Theme.qml @@ -1,82 +1,88 @@ /* * Copyright 2015 Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.4 pragma Singleton /** * A set of named colors for the application * @inherit QtQuick.QtObject */ QtObject { id: theme property color textColor: "#31363b" property color disabledTextColor: "#9931363b" property color highlightColor: "#2196F3" property color highlightedTextColor: "#eff0fa" property color backgroundColor: "#eff0f1" + property color alternateBackgroundColor: "#bdc3c7" property color activeTextColor: "#0176D3" property color linkColor: "#2196F3" property color visitedLinkColor: "#2196F3" property color negativeTextColor: "#DA4453" property color neutralTextColor: "#F67400" property color positiveTextColor: "#27AE60" property color buttonTextColor: "#31363b" property color buttonBackgroundColor: "#eff0f1" + property color buttonAlternateBackgroundColor: "#bdc3c7" property color buttonHoverColor: "#2196F3" property color buttonFocusColor: "#2196F3" property color viewTextColor: "#31363b" property color viewBackgroundColor: "#fcfcfc" + property color viewAlternateBackgroundColor: "#eff0f1" property color viewHoverColor: "#2196F3" property color viewFocusColor: "#2196F3" property color selectionTextColor: "#eff0fa" property color selectionBackgroundColor: "#2196F3" + property color selectionAlternateBackgroundColor: "#1d99f3" property color selectionHoverColor: "#2196F3" property color selectionFocusColor: "#2196F3" property color tooltipTextColor: "#eff0f1" property color tooltipBackgroundColor: "#31363b" + property color tooltipAlternateBackgroundColor: "#4d4d4d" property color tooltipHoverColor: "#2196F3" property color tooltipFocusColor: "#2196F3" property color complementaryTextColor: "#eff0f1" property color complementaryBackgroundColor: "#31363b" + property color complementaryAlternateBackgroundColor: "#3b4045" property color complementaryHoverColor: "#2196F3" property color complementaryFocusColor: "#2196F3" property font defaultFont: fontMetrics.font property list children: [ TextMetrics { id: fontMetrics } ] function __propagateColorSet(object, context) {} function __propagateTextColor(object, color) {} function __propagateBackgroundColor(object, color) {} function __propagatePrimaryColor(object, color) {} function __propagateAccentColor(object, color) {} } diff --git a/src/controls/private/DefaultListItemBackground.qml b/src/controls/private/DefaultListItemBackground.qml index e4bf38ad..96d7fe7b 100644 --- a/src/controls/private/DefaultListItemBackground.qml +++ b/src/controls/private/DefaultListItemBackground.qml @@ -1,49 +1,51 @@ /* * Copyright 2016 Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import org.kde.kirigami 2.4 Rectangle { id: background - color: listItem.checked || listItem.highlighted || (listItem.supportsMouseEvents && listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeBackgroundColor : listItem.backgroundColor + color: listItem.checked || listItem.highlighted || (listItem.supportsMouseEvents && listItem.pressed && !listItem.checked && !listItem.sectionDelegate) + ? listItem.activeBackgroundColor + : (listItem.alternatingBackground && index%2 ? listItem.alternateBackgroundColor : listItem.backgroundColor) visible: listItem.ListView.view ? listItem.ListView.view.highlight === null : true Rectangle { id: internal property bool indicateActiveFocus: listItem.pressed || Settings.tabletMode || listItem.activeFocus || (listItem.ListView.view ? listItem.ListView.view.activeFocus : false) anchors.fill: parent visible: !Settings.tabletMode && listItem.supportsMouseEvents color: listItem.activeBackgroundColor opacity: (listItem.hovered || listItem.highlighted || listItem.activeFocus) && !listItem.pressed ? 0.5 : 0 Behavior on opacity { NumberAnimation { duration: Units.longDuration } } } readonly property bool __separatorVisible: listItem.separatorVisible on__SeparatorVisibleChanged: { if (__separatorVisible) { var newObject = Qt.createQmlObject('import QtQuick 2.0; import org.kde.kirigami 2.4; Separator {anchors {left: parent.left; right: parent.right; bottom: parent.top} visible: listItem.separatorVisible}', background); newObject = Qt.createQmlObject('import QtQuick 2.0; import org.kde.kirigami 2.4; Separator {anchors {left: parent.left; right: parent.right; bottom: parent.bottom} visible: listItem.separatorVisible}', background); } } } diff --git a/src/controls/templates/AbstractListItem.qml b/src/controls/templates/AbstractListItem.qml index ce597c7d..2d2f5462 100644 --- a/src/controls/templates/AbstractListItem.qml +++ b/src/controls/templates/AbstractListItem.qml @@ -1,131 +1,150 @@ /* * Copyright 2010 Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.4 import QtQuick.Templates 2.0 as T2 /** * An item delegate for the primitive ListView component. * * It's intended to make all listviews look coherent. * * @inherit QtQuick.Templates.ItemDelegate */ T2.ItemDelegate { id: listItem /** * supportsMouseEvents: bool * Holds if the item emits signals related to mouse interaction. *TODO: remove * The default value is false. */ property bool supportsMouseEvents: hoverEnabled /** * containsMouse: bool * True when the user hovers the mouse over the list item * NOTE: on mobile touch devices this will be true only when pressed is also true * TODO: remove? */ property alias containsMouse: listItem.hovered + /** + * alternatingBackground: bool + * If true the background of the list items will be alternating between two + * colors, helping readability with multiple column views. + * Use it only when implementing a view which shows data visually in multiple columns + * @ since 2.7 + */ + property bool alternatingBackground: false + /** * sectionDelegate: bool * If true the item will be a delegate for a section, so will look like a * "title" for the items under it. */ property bool sectionDelegate: false /** * separatorVisible: bool * True if the separator between items is visible * default: true */ property bool separatorVisible: true /** * textColor: color * Color for the text in the item * It is advised to leave the default value (Theme.textColor) * * Note: if custom text elements are inserted in an AbstractListItem, * their color property will have to be manually bound with this property */ property color textColor: Theme.textColor /** * backgroundColor: color * Color for the background of the item * It is advised to leave the default value (Theme.viewBackgroundColor) */ property color backgroundColor: "transparent" + /** + * alternateBackgroundColor: color + * The background color to use if alternatingBackground is true. + * It is advised to leave the default. + * @since 2.7 + */ + property color alternateBackgroundColor: Theme.alternateBackgroundColor + /** * activeTextColor: color * Color for the text in the item when pressed or selected * It is advised to leave the default value (Theme.highlightedTextColor) * * Note: if custom text elements are inserted in an AbstractListItem, * their color property will have to be manually bound with this property */ property color activeTextColor: Theme.highlightedTextColor /** * activeBackgroundColor: color * Color for the background of the item when pressed or selected * It is advised to leave the default value (Theme.highlightColor) */ property color activeBackgroundColor: Theme.highlightColor default property alias _default: listItem.contentItem + Theme.inherit: false + Theme.colorSet: Theme.View Theme.colorGroup: internal.indicateActiveFocus ? Theme.Active : Theme.Inactive padding: Settings.tabletMode ? Units.largeSpacing : Units.smallSpacing leftPadding: LayoutMirroring.enabled && internal.view && internal.view.T2.ScrollBar.vertical && internal.view.T2.ScrollBar.vertical.visible ? internal.view.T2.ScrollBar.vertical.width : padding*2 topPadding: padding rightPadding: !LayoutMirroring.enabled && internal.view && internal.view.T2.ScrollBar.vertical && internal.view.T2.ScrollBar.vertical.visible ? internal.view.T2.ScrollBar.vertical.width : padding*2 bottomPadding: padding implicitWidth: contentItem ? contentItem.implicitWidth : Units.gridUnit * 12 implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding width: parent && parent.width > 0 ? parent.width : implicitWidth Layout.fillWidth: true opacity: enabled ? 1 : 0.6 height: visible ? implicitHeight : 0 hoverEnabled: true QtObject { id: internal property Flickable view: listItem.ListView.view || (listItem.parent ? listItem.parent.ListView.view : null) property bool indicateActiveFocus: listItem.pressed || Settings.tabletMode || listItem.activeFocus || (view ? view.activeFocus : false) } Accessible.role: Accessible.ListItem } diff --git a/src/controls/templates/SwipeListItem.qml b/src/controls/templates/SwipeListItem.qml index 209f0dea..4434ddba 100644 --- a/src/controls/templates/SwipeListItem.qml +++ b/src/controls/templates/SwipeListItem.qml @@ -1,448 +1,465 @@ /* * Copyright 2010 Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.4 import "../private" import QtQuick.Templates 2.0 as T2 /** * An item delegate Intended to support extra actions obtainable * by uncovering them by dragging away the item with the handle * This acts as a container for normal list items. * Any subclass of AbstractListItem can be assigned as the contentItem property. * @code * ListView { * model: myModel * delegate: SwipeListItem { * QQC2.Label { * text: model.text * } * actions: [ * Action { * iconName: "document-decrypt" * onTriggered: print("Action 1 clicked") * }, * Action { * iconName: model.action2Icon * onTriggered: //do something * } * ] * } * * } * @endcode * * @inherit QtQuick.Templates.ItemDelegate */ T2.ItemDelegate { id: listItem //BEGIN properties /** * supportsMouseEvents: bool * Holds if the item emits signals related to mouse interaction. *TODO: remove * The default value is false. */ property alias supportsMouseEvents: listItem.hoverEnabled /** * containsMouse: bool * True when the user hover the mouse over the list item * NOTE: on mobile touch devices this will be true only when pressed is also true */ property alias containsMouse: listItem.hovered + /** + * alternatingBackground: bool + * If true the background of the list items will be alternating between two + * colors, helping readability with multiple column views. + * Use it only when implementing a view which shows data visually in multiple columns + * @ since 2.7 + */ + property bool alternatingBackground: false + /** * sectionDelegate: bool * If true the item will be a delegate for a section, so will look like a * "title" for the items under it. */ property bool sectionDelegate: false /** * separatorVisible: bool * True if the separator between items is visible * default: true */ property bool separatorVisible: true /** * actionsVisible: bool * True if it's possible to see and access the item actions. * Actions should go completely out of the way for instance during * the editing of an item. * @since 2.5 */ property alias actionsVisible: behindItem.visible /** * actions: list * Defines the actions for the list item: at most 4 buttons will * contain the actions for the item, that can be revealed by * sliding away the list item. */ property list actions /** * textColor: color * Color for the text in the item * * Note: if custom text elements are inserted in an AbstractListItem, * their color property will have to be manually bound with this property */ property color textColor: Theme.textColor /** * backgroundColor: color * Color for the background of the item */ property color backgroundColor: Theme.backgroundColor + /** + * alternateBackgroundColor: color + * The background color to use if alternatingBackground is true. + * It is advised to leave the default. + * @since 2.7 + */ + property color alternateBackgroundColor: Theme.alternateBackgroundColor + /** * activeTextColor: color * Color for the text in the item when pressed or selected * It is advised to leave the default value (Theme.highlightedTextColor) * * Note: if custom text elements are inserted in an AbstractListItem, * their color property will have to be manually bound with this property */ property color activeTextColor: Theme.highlightedTextColor /** * activeBackgroundColor: color * Color for the background of the item when pressed or selected * It is advised to leave the default value (Theme.highlightColor) */ property color activeBackgroundColor: Theme.highlightColor default property alias _default: listItem.contentItem Theme.colorGroup: behindItem.indicateActiveFocus ? Theme.Active : Theme.Inactive hoverEnabled: true implicitWidth: contentItem ? contentItem.implicitWidth : Units.gridUnit * 12 width: parent ? parent.width : implicitWidth implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding padding: Settings.tabletMode ? Units.largeSpacing : Units.smallSpacing leftPadding: padding * 2 rightPadding: padding * 2 + (handleMouse.visible ? handleMouse.width : hovered * actionsLayout.width) + handleMouse.anchors.rightMargin topPadding: padding bottomPadding: padding //END properties Item { id: behindItem parent: listItem z: -1 //TODO: a global "open" state enabled: background.x !== 0 property bool indicateActiveFocus: listItem.pressed || Settings.tabletMode || listItem.activeFocus || (view ? view.activeFocus : false) property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || listItem.parent) : null) onViewChanged: { if (view && Settings.tabletMode && !behindItem.view.parent.parent._swipeFilter) { var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml")); behindItem.view.parent.parent._swipeFilter = component.createObject(behindItem.view.parent.parent); } } anchors { fill: parent } Rectangle { id: shadowHolder color: Qt.darker(Theme.backgroundColor, 1.05); anchors.fill: parent } EdgeShadow { edge: Qt.TopEdge anchors { right: parent.right left: parent.left top: parent.top } } EdgeShadow { edge: LayoutMirroring.enabled ? Qt.RightEdge : Qt.LeftEdge x: LayoutMirroring.enabled ? listItem.background.x - width : (listItem.background.x + listItem.background.width) anchors { top: parent.top bottom: parent.bottom } } MouseArea { anchors.fill: parent preventStealing: true enabled: background.x != 0 onClicked: { positionAnimation.from = background.x; positionAnimation.to = 0; positionAnimation.running = true; } } Row { id: actionsLayout z: 1 visible: listItem.actionsVisible parent: Settings.tabletMode ? behindItem : listItem opacity: Settings.tabletMode || listItem.hovered || !listItem.supportsMouseEvents ? 1 : 0 Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: Units.gridUnit } height: Math.min( parent.height / 1.5, Units.iconSizes.smallMedium) width: childrenRect.width property bool exclusive: false property Item checkedButton spacing: Units.largeSpacing property int visibleActions: 0 Repeater { model: { if (listItem.actions.length === 0) { return null; } else { return listItem.actions[0].text !== undefined && listItem.actions[0].trigger !== undefined ? listItem.actions : listItem.actions[0]; } } delegate: Icon { height: actionsLayout.height width: height source: modelData.iconName !== "" ? modelData.iconName : modelData.iconSource enabled: (modelData && modelData.enabled !== undefined) ? modelData.enabled : true; visible: (modelData && modelData.visible !== undefined) ? modelData.visible : true; onVisibleChanged: { if (visible) { actionsLayout.visibleActions++; } else { actionsLayout.visibleActions--; } } Component.onCompleted: { if (visible) { actionsLayout.visibleActions++; } } Component.onDestruction: { if (visible) { actionsLayout.visibleActions--; } } MouseArea { id: actionMouse anchors { fill: parent; margins: Settings.tabletMode ? -Units.smallSpacing : 0; } enabled: (modelData && modelData.enabled !== undefined) ? modelData.enabled : true; hoverEnabled: !Settings.tabletMode onClicked: { if (modelData && modelData.trigger !== undefined) { modelData.trigger(); } positionAnimation.from = background.x; positionAnimation.to = 0; positionAnimation.running = true; } Controls.ToolTip.delay: 1000 Controls.ToolTip.timeout: 5000 Controls.ToolTip.visible: listItem.visible && (Settings.tabletMode ? actionMouse.pressed : actionMouse.containsMouse) && Controls.ToolTip.text.length > 0 Controls.ToolTip.text: modelData.tooltip || modelData.text } } } } } MouseArea { id: handleMouse parent: listItem.background visible: Settings.tabletMode && listItem.actionsVisible && actionsLayout.visibleActions > 0 z: 99 anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: !Settings.isMobile && behindItem.view && behindItem.view.T2.ScrollBar && behindItem.view.T2.ScrollBar.vertical && behindItem.view.T2.ScrollBar.vertical.visible ? behindItem.view.T2.ScrollBar.vertical.width : Units.smallSpacing } preventStealing: true width: Units.iconSizes.smallMedium height: width property var downTimestamp; property int startX property int startMouseX onClicked: { positionAnimation.from = background.x; if (listItem.background.x > -listItem.background.width/2) { positionAnimation.to = (LayoutMirroring.enabled ? -1 : +1) * (-listItem.width + height + handleMouse.anchors.rightMargin); } else { positionAnimation.to = 0; } positionAnimation.restart(); } onPressed: { downTimestamp = (new Date()).getTime(); startX = listItem.background.x; startMouseX = mouse.x; } onPositionChanged: { if (LayoutMirroring.enabled) { listItem.background.x = Math.max(0, Math.min(listItem.width - height, listItem.background.x - (startMouseX - mouse.x))); } else { listItem.background.x = Math.min(0, Math.max(-listItem.width + height, listItem.background.x - (startMouseX - mouse.x))); } } onReleased: { var speed = ((startX - listItem.background.x) / ((new Date()).getTime() - downTimestamp) * 1000); var absoluteDelta = startX - listItem.background.x; if (LayoutMirroring.enabled) { speed = -speed; absoluteDelta = -absoluteDelta; } if (Math.abs(speed) < Units.gridUnit) { return; } if (speed > listItem.width/2 || absoluteDelta > listItem.width/2) { positionAnimation.to = (LayoutMirroring.enabled ? -1 : +1) * (-listItem.width + height + handleMouse.anchors.rightMargin); } else { positionAnimation.to = 0; } positionAnimation.from = background.x; positionAnimation.running = true; } Icon { id: handleIcon anchors.fill: parent selected: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) source: (LayoutMirroring.enabled ? (listItem.background.x < listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left") : (listItem.background.x < -listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left")) } } NumberAnimation { id: positionAnimation property: "x" target: background duration: Units.longDuration easing.type: Easing.InOutQuad } //BEGIN signal handlers onContentItemChanged: { if (!contentItem) { return; } contentItem.parent = background; contentItem.anchors.top = background.top; contentItem.anchors.left = background.left; contentItem.anchors.right = background.right; contentItem.anchors.leftMargin = Qt.binding(function() {return listItem.leftPadding}); contentItem.anchors.rightMargin = Qt.binding(function() {return listItem.rightPadding}); contentItem.anchors.topMargin = Qt.binding(function() {return listItem.topPadding}); contentItem.z = 0; } Component.onCompleted: { //this will happen only once listItem.contentItemChanged(); } Connections { target: Settings onTabletModeChanged: { if (Settings.tabletMode) { if (!internal.swipeFilterItem) { var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml")); listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent); } } else { if (listItem.ListView.view.parent.parent._swipeFilter) { listItem.ListView.view.parent.parent._swipeFilter.destroy(); positionAnimation.to = 0; positionAnimation.from = background.x; positionAnimation.running = true; } } } } QtObject { id: internal readonly property QtObject swipeFilterItem: (behindItem.view && behindItem.view.parent && behindItem.view.parent.parent && behindItem.view.parent.parent._swipeFilter) ? behindItem.view.parent.parent._swipeFilter : null readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false } Connections { id: swipeFilterConnection target: internal.edgeEnabled ? internal.swipeFilterItem : null onPeekChanged: { if (!listItem.actionsVisible) { return; } if (listItem.LayoutMirroring.enabled) { listItem.background.x = (listItem.background.width - listItem.background.height) * (1 - internal.swipeFilterItem.peek); } else { listItem.background.x = -(listItem.background.width - listItem.background.height) * internal.swipeFilterItem.peek; } } onPressed: { if (internal.edgeEnabled) { handleMouse.onPressed(mouse); } } onClicked: { if (Math.abs(listItem.background.x) < Units.gridUnit && internal.edgeEnabled) { handleMouse.clicked(mouse); } } onReleased: { if (internal.edgeEnabled) { handleMouse.released(mouse); } } onCurrentItemChanged: { if (!internal.edgeEnabled) { positionAnimation.to = 0; positionAnimation.from = background.x; positionAnimation.running = true; } } } //END signal handlers Accessible.role: Accessible.ListItem } diff --git a/src/libkirigami/basictheme.cpp b/src/libkirigami/basictheme.cpp index 56ab3885..a32c3c45 100644 --- a/src/libkirigami/basictheme.cpp +++ b/src/libkirigami/basictheme.cpp @@ -1,325 +1,336 @@ /* * Copyright (C) 2017 by Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "basictheme_p.h" #include #include #include #include #include #include #include namespace Kirigami { class BasicThemeDeclarativeSingleton { public: BasicThemeDeclarativeSingleton() {} BasicThemeDeclarative self; }; Q_GLOBAL_STATIC(BasicThemeDeclarativeSingleton, privateBasicThemeDeclarativeSelf) BasicThemeDeclarative::BasicThemeDeclarative() { m_colorSyncTimer = new QTimer; m_colorSyncTimer->setInterval(0); m_colorSyncTimer->setSingleShot(true); } BasicThemeDeclarative::~BasicThemeDeclarative() { delete m_colorSyncTimer; } QObject *BasicThemeDeclarative::instance(const BasicTheme *theme) { if (m_declarativeBasicTheme) { return m_declarativeBasicTheme; } QQmlEngine *engine = qmlEngine(theme->parent()); Q_ASSERT(engine); QQmlComponent c(engine); //NOTE: for now is important this import stays at 2.0 c.setData("import QtQuick 2.6\n\ import org.kde.kirigami 2.0 as Kirigami\n\ QtObject {\n\ property QtObject theme: Kirigami.Theme\n\ }", QUrl()); QObject *obj = c.create(); m_declarativeBasicTheme = obj->property("theme").value(); return m_declarativeBasicTheme; } BasicTheme::BasicTheme(QObject *parent) : PlatformTheme(parent) { //TODO: correct? connect(qApp, &QGuiApplication::fontDatabaseChanged, this, [this]() {setDefaultFont(qApp->font());}); //connect all the declarative object signals to the timer start to compress, use the old syntax as they are all signals defined in QML connect(basicThemeDeclarative()->instance(this), SIGNAL(textColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(disabledTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(highlightColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(highlightedTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(backgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); + connect(basicThemeDeclarative()->instance(this), SIGNAL(alternateBackgroundColorChanged()), + basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(linkColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(visitedLinkColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); + connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonAlternateBackgroundColorChanged()), + basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); + connect(basicThemeDeclarative()->instance(this), SIGNAL(viewAlternateBackgroundColorChanged()), + basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); + connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryAlternateBackgroundColorChanged()), + basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); //finally connect the timer to the sync connect(basicThemeDeclarative()->m_colorSyncTimer, &QTimer::timeout, this, &BasicTheme::syncColors); connect(this, &BasicTheme::colorSetChanged, this, &BasicTheme::syncColors); connect(this, &BasicTheme::colorGroupChanged, this, &BasicTheme::syncColors); connect(this, &PlatformTheme::colorSetChanged, this, &BasicTheme::syncCustomColorsToQML); connect(this, &PlatformTheme::colorsChanged, this, &BasicTheme::syncCustomColorsToQML); syncColors(); } BasicTheme::~BasicTheme() { } static inline QColor colorGroupTint(const QColor &color, PlatformTheme::ColorGroup group) { switch (group) { case PlatformTheme::Inactive: return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF()); case PlatformTheme::Disabled: return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF()*0.8); default: return color; } } //TODO: tint for which we need to chain to m_parentBasicTheme's color #define RESOLVECOLOR(colorName, upperCaseColor) \ QColor color;\ switch (colorSet()) {\ case BasicTheme::Button:\ color = basicThemeDeclarative()->instance(this)->property("button"#upperCaseColor).value();\ break;\ case BasicTheme::View:\ color = basicThemeDeclarative()->instance(this)->property("view"#upperCaseColor).value();\ break;\ case BasicTheme::Selection:\ color = basicThemeDeclarative()->instance(this)->property("selection"#upperCaseColor).value();\ break;\ case BasicTheme::Tooltip:\ color = basicThemeDeclarative()->instance(this)->property("tooltip"#upperCaseColor).value();\ break;\ case BasicTheme::Complementary:\ color = basicThemeDeclarative()->instance(this)->property("complementary"#upperCaseColor).value();\ break;\ case BasicTheme::Window:\ default:\ color = basicThemeDeclarative()->instance(this)->property(#colorName).value();\ }\ color = colorGroupTint(color, colorGroup()); #define PROXYCOLOR(colorName, upperCaseColor) \ colorGroupTint(basicThemeDeclarative()->instance(this)->property(#colorName).value(), colorGroup()) void BasicTheme::syncColors() { { RESOLVECOLOR(textColor, TextColor); setTextColor(color); }{ setDisabledTextColor(PROXYCOLOR(disabledTextColor, DisabledTextColor)); }{ RESOLVECOLOR(backgroundColor, BackgroundColor) setBackgroundColor(color); + }{ + RESOLVECOLOR(alternateBackgroundColor, AlternateBackgroundColor) + setAlternateBackgroundColor(color); }{ setHighlightColor(PROXYCOLOR(highlightColor, HighlightColor)); }{ setHighlightedTextColor(PROXYCOLOR(highlightedTextColor, HighlightedTextColor)); }{ setActiveTextColor(PROXYCOLOR(activeTextColor, ActiveTextColor)); }{ setLinkColor(PROXYCOLOR(linkColor, LinkColor)); }{ setVisitedLinkColor(PROXYCOLOR(visitedLinkColor, VisitedLinkColor)); }{ setNegativeTextColor(PROXYCOLOR(negativeTextColor, NegativeTextColor)); }{ setNeutralTextColor(PROXYCOLOR(neutralTextColor, NeutralTextColor)); }{ setPositiveTextColor(PROXYCOLOR(positiveTextColor, PositiveTextColor)); }{ RESOLVECOLOR(hoverColor, HoverColor); setHoverColor(color); }{ RESOLVECOLOR(focusColor, FocusColor); setFocusColor(color); } //legacy { m_buttonTextColor = PROXYCOLOR(buttonTextColor, ButtonTextColor); m_buttonBackgroundColor = PROXYCOLOR(buttonBackgroundColor, ButtonBackgroundColor); m_buttonHoverColor = PROXYCOLOR(buttonHoverColor, ButtonHoverColor); m_buttonFocusColor = PROXYCOLOR(buttonFocusColor, ButtonFocusColor); m_viewTextColor = PROXYCOLOR(viewTextColor, ViewTextColor); m_viewBackgroundColor = PROXYCOLOR(viewBackgroundColor, ViewBackgroundColor); m_viewHoverColor = PROXYCOLOR(viewHoverColor, ViewHoverColor); m_viewFocusColor = PROXYCOLOR(viewFocusColor, ViewFocusColor); } QPalette pal = qApp->palette(); pal.setColor(QPalette::WindowText, textColor()); pal.setColor(QPalette::Button, m_buttonBackgroundColor); pal.setColor(QPalette::Light, m_buttonBackgroundColor.lighter(120)); pal.setColor(QPalette::Dark, m_buttonBackgroundColor.darker(120)); pal.setColor(QPalette::Mid, m_buttonBackgroundColor.darker(110)); pal.setColor(QPalette::Base, m_viewBackgroundColor); pal.setColor(QPalette::HighlightedText, highlightedTextColor()); pal.setColor(QPalette::Text, m_viewTextColor); pal.setColor(QPalette::Window, backgroundColor()); setPalette(pal); if (this->parent()) { //this will work on Qt 5.10+ but is a safe noop on older releases this->parent()->setProperty("palette", QVariant::fromValue(pal)); if (basicThemeDeclarative()->instance(this)) { QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateColorSet", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, colorSet())); } } emit colorsChanged(); } void BasicTheme::syncCustomColorsToQML() { if (basicThemeDeclarative()->instance(this)) { QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateTextColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, textColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateBackgroundColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, backgroundColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagatePrimaryColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, highlightColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateAccentColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, highlightColor())); } } QColor BasicTheme::buttonTextColor() const { qWarning()<<"WARNING: buttonTextColor is deprecated, use textColor with colorSet: Theme.Button instead"; return m_buttonTextColor; } QColor BasicTheme::buttonBackgroundColor() const { qWarning()<<"WARNING: buttonBackgroundColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonBackgroundColor; } QColor BasicTheme::buttonHoverColor() const { qWarning()<<"WARNING: buttonHoverColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonHoverColor; } QColor BasicTheme::buttonFocusColor() const { qWarning()<<"WARNING: buttonFocusColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonFocusColor; } QColor BasicTheme::viewTextColor() const { qWarning()<<"WARNING: viewTextColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewTextColor; } QColor BasicTheme::viewBackgroundColor() const { qWarning()<<"WARNING: viewBackgroundColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewBackgroundColor; } QColor BasicTheme::viewHoverColor() const { qWarning()<<"WARNING: viewHoverColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewHoverColor; } QColor BasicTheme::viewFocusColor() const { qWarning()<<"WARNING: viewFocusColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewFocusColor; } BasicThemeDeclarative *BasicTheme::basicThemeDeclarative() { return &privateBasicThemeDeclarativeSelf->self; } } #include "moc_basictheme_p.cpp" diff --git a/src/libkirigami/platformtheme.cpp b/src/libkirigami/platformtheme.cpp index 43fcf22e..4923c670 100644 --- a/src/libkirigami/platformtheme.cpp +++ b/src/libkirigami/platformtheme.cpp @@ -1,728 +1,742 @@ /* * Copyright (C) 2017 by Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "platformtheme.h" #include "kirigamipluginfactory.h" #include "basictheme_p.h" #include #include #include #include #include #include #include #include #include #include namespace Kirigami { class PlatformThemePrivate { public: PlatformThemePrivate(PlatformTheme *q); ~PlatformThemePrivate(); inline void syncCustomPalette(); void findParentStyle(); static QColor tint(const QColor &c1, const QColor &c2, qreal ratio); PlatformTheme *q; QTimer *setColorCompressTimer; PlatformTheme::ColorSet m_colorSet = PlatformTheme::Window; PlatformTheme::ColorGroup m_colorGroup = PlatformTheme::Active; QSet m_childThemes; QPointer m_parentTheme; //ordinary colors QColor textColor; QColor disabledTextColor; QColor highlightedTextColor; QColor activeTextColor; QColor linkColor; QColor visitedLinkColor; QColor negativeTextColor; QColor neutralTextColor; QColor positiveTextColor; QColor backgroundColor; + QColor alternateBackgroundColor; QColor highlightColor; QColor focusColor; QColor hoverColor; QPalette palette; //custom colors QColor customTextColor; QColor customDisabledTextColor; QColor customHighlightedTextColor; QColor customActiveTextColor; QColor customLinkColor; QColor customVisitedLinkColor; QColor customNegativeTextColor; QColor customNeutralTextColor; QColor customPositiveTextColor; QColor customBackgroundColor; + QColor customAlternateBackgroundColor; QColor customHighlightColor; QColor customFocusColor; QColor customHoverColor; QPalette customPalette; QFont font; bool m_inherit = true; bool m_init = true; static KirigamiPluginFactory *s_pluginFactory; }; KirigamiPluginFactory *PlatformThemePrivate::s_pluginFactory = nullptr; PlatformThemePrivate::PlatformThemePrivate(PlatformTheme *q) : q(q) { setColorCompressTimer = new QTimer(q); setColorCompressTimer->setSingleShot(true); setColorCompressTimer->setInterval(0); } PlatformThemePrivate::~PlatformThemePrivate() {} void setPaletteColor(QPalette& customPalette, QPalette::ColorGroup cg, QPalette::ColorRole cr, const QColor &color) { if (customPalette.color(cg, cr) != color) { customPalette.setColor(cg, cr, color); } } void PlatformThemePrivate::syncCustomPalette() { for (auto state : { QPalette::Active, QPalette::Inactive, QPalette::Disabled }) { setPaletteColor(customPalette, state, QPalette::WindowText, q->textColor()); setPaletteColor(customPalette, state, QPalette::Window, q->backgroundColor()); setPaletteColor(customPalette, state, QPalette::Base, q->backgroundColor()); setPaletteColor(customPalette, state, QPalette::Text, q->textColor()); setPaletteColor(customPalette, state, QPalette::Button, q->backgroundColor()); setPaletteColor(customPalette, state, QPalette::ButtonText, q->textColor()); setPaletteColor(customPalette, state, QPalette::Highlight, q->highlightColor()); setPaletteColor(customPalette, state, QPalette::HighlightedText, q->highlightedTextColor()); setPaletteColor(customPalette, state, QPalette::ToolTipBase, q->backgroundColor()); setPaletteColor(customPalette, state, QPalette::ToolTipText, q->textColor()); setPaletteColor(customPalette, state, QPalette::Link, q->linkColor()); setPaletteColor(customPalette, state, QPalette::LinkVisited, q->visitedLinkColor()); } emit q->paletteChanged(customPalette); } void PlatformThemePrivate::findParentStyle() { if (m_parentTheme) { m_parentTheme->d->m_childThemes.remove(q); } QQuickItem *candidate = qobject_cast(q->parent()); while (candidate) { candidate = candidate->parentItem(); PlatformTheme *t = static_cast(qmlAttachedPropertiesObject(candidate, false)); if (t) { t->d->m_childThemes.insert(q); m_parentTheme = t; if (m_inherit) { q->setColorSet(t->colorSet()); q->setCustomTextColor(t->d->customTextColor); q->setCustomDisabledTextColor(t->d->customDisabledTextColor); q->setCustomHighlightedTextColor(t->d->customHighlightedTextColor); q->setCustomActiveTextColor(t->d->customActiveTextColor); q->setCustomLinkColor(t->d->customLinkColor); q->setCustomVisitedLinkColor(t->d->customVisitedLinkColor); q->setCustomNegativeTextColor(t->d->customNegativeTextColor); q->setCustomNeutralTextColor(t->d->customNeutralTextColor); q->setCustomPositiveTextColor(t->d->customPositiveTextColor); q->setCustomBackgroundColor(t->d->customBackgroundColor); + q->setCustomAlternateBackgroundColor(t->d->customAlternateBackgroundColor); q->setCustomHighlightColor(t->d->customHighlightColor); q->setCustomFocusColor(t->d->customFocusColor); q->setCustomHoverColor(t->d->customHoverColor); } break; } } } QColor PlatformThemePrivate::tint(const QColor &c1, const QColor &c2, qreal ratio) { qreal r = c1.redF() + (c2.redF() - c1.redF()) * ratio; qreal g = c1.greenF() + (c2.greenF() - c1.greenF()) * ratio; qreal b = c1.blueF() + (c2.blueF() - c1.blueF()) * ratio; return QColor::fromRgbF(r, g, b, 1); } PlatformTheme::PlatformTheme(QObject *parent) : QObject(parent), d(new PlatformThemePrivate(this)) { connect(d->setColorCompressTimer, &QTimer::timeout, this, [this]() { d->syncCustomPalette(); emit colorsChanged(); }); d->findParentStyle(); if (QQuickItem *item = qobject_cast(parent)) { connect(item, &QQuickItem::windowChanged, this, [this]() { d->findParentStyle(); }); connect(item, &QQuickItem::parentChanged, this, [this]() { d->findParentStyle(); }); } d->m_init = false; //TODO: needs https://codereview.qt-project.org/#/c/206889/ for font changes } PlatformTheme::~PlatformTheme() { if (d->m_parentTheme) { d->m_parentTheme->d->m_childThemes.remove(this); } delete d; } void PlatformTheme::setColorSet(PlatformTheme::ColorSet colorSet) { if (d->m_colorSet == colorSet) { return; } d->m_colorSet = colorSet; for (PlatformTheme *t : qAsConst(d->m_childThemes)) { if (t->inherit()) { t->setColorSet(colorSet); - /* if (colorSet == Custom) { - t->setCustomTextColor(textColor()); - t->setCustomDisabledTextColor(disabledTextColor()); - t->setCustomHighlightedTextColor(highlightedTextColor()); - t->setCustomActiveTextColor(activeTextColor()); - t->setCustomLinkColor(linkColor()); - t->setCustomVisitedLinkColor(visitedLinkColor()); - t->setCustomNegativeTextColor(negativeTextColor()); - t->setCustomNeutralTextColor(neutralTextColor()); - t->setCustomPositiveTextColor(positiveTextColor()); - t->setCustomBackgroundColor(backgroundColor()); - t->setCustomHighlightColor(highlightColor()); - t->setCustomFocusColor(focusColor()); - t->setCustomHoverColor(hoverColor()); - }*/ } } if (!d->m_init) { emit colorSetChanged(colorSet); d->setColorCompressTimer->start(); } } PlatformTheme::ColorSet PlatformTheme::colorSet() const { return d->m_colorSet; } void PlatformTheme::setColorGroup(PlatformTheme::ColorGroup colorGroup) { if (d->m_colorGroup == colorGroup) { return; } d->m_colorGroup = colorGroup; for (PlatformTheme *t : qAsConst(d->m_childThemes)) { if (t->inherit()) { t->setColorGroup(colorGroup); } } if (!d->m_init) { emit colorGroupChanged(colorGroup); d->setColorCompressTimer->start(); } } PlatformTheme::ColorGroup PlatformTheme::colorGroup() const { return d->m_colorGroup; } bool PlatformTheme::inherit() const { return d->m_inherit; } void PlatformTheme::setInherit(bool inherit) { if (d->m_inherit == inherit) { return; } d->m_inherit = inherit; if (inherit && d->m_parentTheme) { setColorSet(d->m_parentTheme->colorSet()); } emit inheritChanged(inherit); } QColor PlatformTheme::textColor() const { return d->customTextColor.isValid() ? d->customTextColor : d->textColor; } QColor PlatformTheme::disabledTextColor() const { return d->customDisabledTextColor.isValid() ? d->customDisabledTextColor : d->disabledTextColor; } QColor PlatformTheme::highlightColor() const { return d->customHighlightColor.isValid() ? d->customHighlightColor : d->highlightColor; } QColor PlatformTheme::highlightedTextColor() const { return d->customHighlightedTextColor.isValid() ? d->customHighlightedTextColor : d->highlightedTextColor; } QColor PlatformTheme::backgroundColor() const { return d->customBackgroundColor.isValid() ? d->customBackgroundColor : d->backgroundColor; } +QColor PlatformTheme::alternateBackgroundColor() const +{ + return d->customAlternateBackgroundColor.isValid() ? d->customAlternateBackgroundColor : d->alternateBackgroundColor; +} + QColor PlatformTheme::activeTextColor() const { return d->customActiveTextColor.isValid() ? d->customActiveTextColor : d->activeTextColor; } QColor PlatformTheme::linkColor() const { return d->customLinkColor.isValid() ? d->customLinkColor : d->linkColor; } QColor PlatformTheme::visitedLinkColor() const { return d->customVisitedLinkColor.isValid() ? d->customVisitedLinkColor : d->visitedLinkColor; } QColor PlatformTheme::negativeTextColor() const { return d->customNegativeTextColor.isValid() ? d->customNegativeTextColor : d->negativeTextColor; } QColor PlatformTheme::neutralTextColor() const { return d->customNeutralTextColor.isValid() ? d->customNeutralTextColor : d->neutralTextColor; } QColor PlatformTheme::positiveTextColor() const { return d->customPositiveTextColor.isValid() ? d->customPositiveTextColor : d->positiveTextColor; } QColor PlatformTheme::focusColor() const { return d->customFocusColor.isValid() ? d->customFocusColor : d->focusColor; } QColor PlatformTheme::hoverColor() const { return d->customHoverColor.isValid() ? d->customHoverColor : d->hoverColor; } //setters for theme implementations void PlatformTheme::setTextColor(const QColor &color) { if (d->textColor == color) { return; } d->textColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setDisabledTextColor(const QColor &color) { if (d->disabledTextColor == color) { return; } d->disabledTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setBackgroundColor(const QColor &color) { if (d->backgroundColor == color) { return; } d->backgroundColor = color; d->setColorCompressTimer->start(); } +void PlatformTheme::setAlternateBackgroundColor(const QColor &color) +{ + if (d->alternateBackgroundColor == color) { + return; + } + + d->alternateBackgroundColor = color; + d->setColorCompressTimer->start(); +} + void PlatformTheme::setHighlightColor(const QColor &color) { if (d->highlightColor == color) { return; } d->highlightColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setHighlightedTextColor(const QColor &color) { if (d->highlightedTextColor == color) { return; } d->highlightedTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setActiveTextColor(const QColor &color) { if (d->activeTextColor == color) { return; } d->activeTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setLinkColor(const QColor &color) { if (d->linkColor == color) { return; } d->linkColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setVisitedLinkColor(const QColor &color) { if (d->visitedLinkColor == color) { return; } d->visitedLinkColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setNegativeTextColor(const QColor &color) { if (d->negativeTextColor == color) { return; } d->negativeTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setNeutralTextColor(const QColor &color) { if (d->neutralTextColor == color) { return; } d->neutralTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setPositiveTextColor(const QColor &color) { if (d->positiveTextColor == color) { return; } d->positiveTextColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setHoverColor(const QColor &color) { if (d->hoverColor == color) { return; } d->hoverColor = color; d->setColorCompressTimer->start(); } void PlatformTheme::setFocusColor(const QColor &color) { if (d->focusColor == color) { return; } d->focusColor = color; d->setColorCompressTimer->start(); } QFont PlatformTheme::defaultFont() const { return d->font; } void PlatformTheme::setDefaultFont(const QFont &font) { if (d->font == font) { return; } d->font = font; emit defaultFontChanged(font); } #define PROPAGATECUSTOMCOLOR(colorName, color)\ for (PlatformTheme *t : qAsConst(d->m_childThemes)) {\ if (t->inherit()) {\ t->set##colorName(color);\ }\ } //setters for QML clients void PlatformTheme::setCustomTextColor(const QColor &color) { if (d->customTextColor == color) { return; } d->customTextColor = color; PROPAGATECUSTOMCOLOR(CustomTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomDisabledTextColor(const QColor &color) { if (d->customDisabledTextColor == color) { return; } d->customDisabledTextColor = color; PROPAGATECUSTOMCOLOR(CustomDisabledTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomBackgroundColor(const QColor &color) { if (d->customBackgroundColor == color) { return; } d->customBackgroundColor = color; PROPAGATECUSTOMCOLOR(CustomBackgroundColor, color) d->setColorCompressTimer->start(); } +void PlatformTheme::setCustomAlternateBackgroundColor(const QColor &color) +{ + if (d->customAlternateBackgroundColor == color) { + return; + } + + d->customAlternateBackgroundColor = color; + PROPAGATECUSTOMCOLOR(CustomAlternateBackgroundColor, color) + d->setColorCompressTimer->start(); +} + void PlatformTheme::setCustomHighlightColor(const QColor &color) { if (d->customHighlightColor == color) { return; } d->customHighlightColor = color; PROPAGATECUSTOMCOLOR(CustomHighlightColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomHighlightedTextColor(const QColor &color) { if (d->customHighlightedTextColor == color) { return; } d->customHighlightedTextColor = color; PROPAGATECUSTOMCOLOR(CustomHighlightedTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomActiveTextColor(const QColor &color) { if (d->customActiveTextColor == color) { return; } d->customActiveTextColor = color; PROPAGATECUSTOMCOLOR(CustomActiveTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomLinkColor(const QColor &color) { if (d->customLinkColor == color) { return; } d->customLinkColor = color; PROPAGATECUSTOMCOLOR(CustomLinkColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomVisitedLinkColor(const QColor &color) { if (d->customVisitedLinkColor == color) { return; } d->customVisitedLinkColor = color; PROPAGATECUSTOMCOLOR(CustomVisitedLinkColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomNegativeTextColor(const QColor &color) { if (d->customNegativeTextColor == color) { return; } d->customNegativeTextColor = color; PROPAGATECUSTOMCOLOR(CustomNegativeTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomNeutralTextColor(const QColor &color) { if (d->customNeutralTextColor == color) { return; } d->customNeutralTextColor = color; PROPAGATECUSTOMCOLOR(CustomNeutralTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomPositiveTextColor(const QColor &color) { if (d->customPositiveTextColor == color) { return; } d->customPositiveTextColor = color; PROPAGATECUSTOMCOLOR(CustomPositiveTextColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomHoverColor(const QColor &color) { if (d->customHoverColor == color) { return; } d->customHoverColor = color; PROPAGATECUSTOMCOLOR(CustomHoverColor, color) d->setColorCompressTimer->start(); } void PlatformTheme::setCustomFocusColor(const QColor &color) { if (d->customFocusColor == color) { return; } d->customFocusColor = color; PROPAGATECUSTOMCOLOR(CustomFocusColor, color) d->setColorCompressTimer->start(); } QPalette PlatformTheme::palette() const { //check the most important custom colors to decide to return a custom palette return d->customTextColor.isValid() || d->customBackgroundColor.isValid() || d->customHighlightColor.isValid() ? d->customPalette : d->palette; } void PlatformTheme::setPalette(const QPalette &palette) { if (d->palette == palette) { return; } d->palette = palette; PROPAGATECUSTOMCOLOR(Palette, palette) emit paletteChanged(palette); } QIcon PlatformTheme::iconFromTheme(const QString &name, const QColor &customColor) { QIcon icon = QIcon::fromTheme(name); if (!icon.isNull() && (name.endsWith(QLatin1String("-symbolic")) || customColor != Qt::transparent)) { icon.setIsMask(true); } return icon; } PlatformTheme *PlatformTheme::qmlAttachedProperties(QObject *object) { static bool s_factoryChecked = false; //check for the plugin only once: it's an heavy operation if (PlatformThemePrivate::s_pluginFactory) { return PlatformThemePrivate::s_pluginFactory->createPlatformTheme(object); } else if (!s_factoryChecked) { s_factoryChecked = true; #if QT_CONFIG(library) const auto libraryPaths = QCoreApplication::libraryPaths(); for (const QString &path : libraryPaths) { QDir dir(path + QStringLiteral("/kf5/kirigami")); const auto fileNames = dir.entryList(QDir::Files); for (const QString &fileName : fileNames) { //TODO: env variable? if (!QQuickStyle::name().isEmpty() && fileName.startsWith(QQuickStyle::name())) { QPluginLoader loader(dir.absoluteFilePath(fileName)); QObject *plugin = loader.instance(); //TODO: load actually a factory as plugin KirigamiPluginFactory *factory = qobject_cast(plugin); if (factory) { PlatformThemePrivate::s_pluginFactory = factory; return factory->createPlatformTheme(object); } } } } #endif } return new BasicTheme(object); } } #include "moc_platformtheme.cpp" diff --git a/src/libkirigami/platformtheme.h b/src/libkirigami/platformtheme.h index 466ada4f..a9b20a81 100644 --- a/src/libkirigami/platformtheme.h +++ b/src/libkirigami/platformtheme.h @@ -1,270 +1,281 @@ /* * Copyright (C) 2017 by Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PLATFORMTHEME_H #define PLATFORMTHEME_H #include #include #include #include #ifndef KIRIGAMI_BUILD_TYPE_STATIC #include #endif namespace Kirigami { class PlatformThemePrivate; /** * @class PlatformTheme platformtheme.h PlatformTheme * * This class is the base for color management in Kirigami, * different platforms can reimplement this class to integrate with * system platform colors of a given platform */ #ifdef KIRIGAMI_BUILD_TYPE_STATIC class PlatformTheme : public QObject #else class KIRIGAMI2_EXPORT PlatformTheme : public QObject #endif { Q_OBJECT /** * This enumeration describes the color set for which a color is being selected. * * Color sets define a color "environment", suitable for drawing all parts of a * given region. Colors from different sets should not be combined. */ Q_PROPERTY(ColorSet colorSet READ colorSet WRITE setColorSet NOTIFY colorSetChanged) /** * This enumeration describes the color group used to generate the colors. * The enum value is based upon QPalette::CpolorGroup and has the same values. * It's redefined here in order to make it work with QML * @since 4.43 */ Q_PROPERTY(ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged) /** * If true, the colorSet will be inherited from the colorset of a theme of one * of the ancestor items * default: true */ Q_PROPERTY(bool inherit READ inherit WRITE setInherit NOTIFY inheritChanged) // foreground colors /** * Color for normal foregrounds, usually text, but not limited to it, * anything that should be painted with a clear contrast should use this color */ Q_PROPERTY(QColor textColor READ textColor WRITE setCustomTextColor RESET setCustomTextColor NOTIFY colorsChanged) /** * Foreground color for disabled areas, usually a mid-gray */ Q_PROPERTY(QColor disabledTextColor READ disabledTextColor WRITE setCustomDisabledTextColor RESET setCustomDisabledTextColor NOTIFY colorsChanged) /** * Color for text that has been highlighted, often is a light color while normal text is dark */ Q_PROPERTY(QColor highlightedTextColor READ highlightedTextColor WRITE setCustomHighlightedTextColor RESET setCustomHighlightedTextColor NOTIFY colorsChanged) /** * Foreground for areas that are active or requesting attention */ Q_PROPERTY(QColor activeTextColor READ activeTextColor WRITE setCustomActiveTextColor RESET setCustomActiveTextColor NOTIFY colorsChanged) /** * Color for links */ Q_PROPERTY(QColor linkColor READ linkColor WRITE setCustomLinkColor RESET setCustomLinkColor NOTIFY colorsChanged) /** * Color for visited links, usually a bit darker than linkColor */ Q_PROPERTY(QColor visitedLinkColor READ visitedLinkColor WRITE setCustomVisitedLinkColor RESET setCustomVisitedLinkColor NOTIFY colorsChanged) /** * Foreground color for negative areas, such as critical error text */ Q_PROPERTY(QColor negativeTextColor READ negativeTextColor WRITE setCustomNegativeTextColor RESET setCustomNegativeTextColor NOTIFY colorsChanged) /** * Foreground color for neutral areas, such as warning texts (but not critical) */ Q_PROPERTY(QColor neutralTextColor READ neutralTextColor WRITE setCustomNeutralTextColor RESET setCustomNeutralTextColor NOTIFY colorsChanged) /** * Success messages, trusted content */ Q_PROPERTY(QColor positiveTextColor READ positiveTextColor WRITE setCustomPositiveTextColor RESET setCustomPositiveTextColor NOTIFY colorsChanged) //background colors /** * The generic background color */ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setCustomBackgroundColor RESET setCustomBackgroundColor NOTIFY colorsChanged) + /** + * The generic background color + * Alternate background; for example, for use in lists. + * This color may be the same as BackgroundNormal, + * especially in sets other than View and Window. + */ + Q_PROPERTY(QColor alternateBackgroundColor READ alternateBackgroundColor WRITE setCustomAlternateBackgroundColor RESET setCustomAlternateBackgroundColor NOTIFY colorsChanged) + /** * The background color for selected areas */ Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setCustomHighlightColor RESET setCustomHighlightColor NOTIFY colorsChanged) //decoration colors /** * A decoration color that indicates active focus */ Q_PROPERTY(QColor focusColor READ focusColor WRITE setCustomFocusColor RESET setCustomFocusColor NOTIFY colorsChanged) /** * A decoration color that indicates mouse hovering */ Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setCustomHoverColor RESET setCustomHoverColor NOTIFY colorsChanged) // font and palette Q_PROPERTY(QFont defaultFont READ defaultFont NOTIFY defaultFontChanged) //Active palette Q_PROPERTY(QPalette palette READ palette NOTIFY paletteChanged) public: enum ColorSet { View = 0, /** Color set for item views, usually the lightest of all */ Window, /** Default Color set for windows and "chrome" areas */ Button, /** Color set used by buttons */ Selection, /** Color set used by selectged areas */ Tooltip, /** Color set used by tooltips */ Complementary /** Color set meant to be complementary to Window: usually is a dark theme for light themes */ }; Q_ENUM(ColorSet) enum ColorGroup { Disabled = QPalette::Disabled, Active = QPalette::Active, Inactive = QPalette::Inactive, Normal = QPalette::Normal }; Q_ENUM(ColorGroup) explicit PlatformTheme(QObject *parent = nullptr); ~PlatformTheme(); void setColorSet(PlatformTheme::ColorSet); PlatformTheme::ColorSet colorSet() const; void setColorGroup(PlatformTheme::ColorGroup); PlatformTheme::ColorGroup colorGroup() const; bool inherit() const; void setInherit(bool inherit); //foreground colors QColor textColor() const; QColor disabledTextColor() const; QColor highlightedTextColor() const; QColor activeTextColor() const; QColor linkColor() const; QColor visitedLinkColor() const; QColor negativeTextColor() const; QColor neutralTextColor() const; QColor positiveTextColor() const; //background colors QColor backgroundColor() const; + QColor alternateBackgroundColor() const; QColor highlightColor() const; //TODO: add active/positive/neutral/negative backgrounds? //decoration colors QColor focusColor() const; QColor hoverColor() const; QFont defaultFont() const; //this may is used by the desktop QQC2 to set the styleoption palettes QPalette palette() const; //this will be used by desktopicon to fetch icons with KIconLoader virtual Q_INVOKABLE QIcon iconFromTheme(const QString &name, const QColor &customColor = Qt::transparent); //foreground colors void setCustomTextColor(const QColor &color = QColor()); void setCustomDisabledTextColor(const QColor &color = QColor()); void setCustomHighlightedTextColor(const QColor &color = QColor()); void setCustomActiveTextColor(const QColor &color = QColor()); void setCustomLinkColor(const QColor &color = QColor()); void setCustomVisitedLinkColor(const QColor &color = QColor()); void setCustomNegativeTextColor(const QColor &color = QColor()); void setCustomNeutralTextColor(const QColor &color = QColor()); void setCustomPositiveTextColor(const QColor &color = QColor()); //background colors void setCustomBackgroundColor(const QColor &color = QColor()); + void setCustomAlternateBackgroundColor(const QColor &color = QColor()); void setCustomHighlightColor(const QColor &color = QColor()); //decoration colors void setCustomFocusColor(const QColor &color = QColor()); void setCustomHoverColor(const QColor &color = QColor()); //QML attached property static PlatformTheme *qmlAttachedProperties(QObject *object); Q_SIGNALS: //TODO: parameters to signals as this is also a c++ api void colorsChanged(); void defaultFontChanged(const QFont &font); void colorSetChanged(Kirigami::PlatformTheme::ColorSet colorSet); void colorGroupChanged(Kirigami::PlatformTheme::ColorGroup colorGroup); void paletteChanged(const QPalette &pal); void inheritChanged(bool inherit); protected: //Setters, not accessible from QML but from implementations //foreground colors void setTextColor(const QColor &color); void setDisabledTextColor(const QColor &color); void setHighlightedTextColor(const QColor &color); void setActiveTextColor(const QColor &color); void setLinkColor(const QColor &color); void setVisitedLinkColor(const QColor &color); void setNegativeTextColor(const QColor &color); void setNeutralTextColor(const QColor &color); void setPositiveTextColor(const QColor &color); //background colors void setBackgroundColor(const QColor &color); + void setAlternateBackgroundColor(const QColor &color); void setHighlightColor(const QColor &color); //decoration colors void setFocusColor(const QColor &color); void setHoverColor(const QColor &color); void setDefaultFont(const QFont &defaultFont); void setPalette(const QPalette &palette); private: PlatformThemePrivate *d; friend class PlatformThemePrivate; }; } QML_DECLARE_TYPEINFO(Kirigami::PlatformTheme, QML_HAS_ATTACHED_PROPERTIES) #endif // PLATFORMTHEME_H diff --git a/src/styles/org.kde.desktop/Theme.qml b/src/styles/org.kde.desktop/Theme.qml index c7db9008..3a102992 100644 --- a/src/styles/org.kde.desktop/Theme.qml +++ b/src/styles/org.kde.desktop/Theme.qml @@ -1,90 +1,96 @@ /* * Copyright 2015 Marco Martin * * This program 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, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.4 pragma Singleton QtObject { id: theme property color textColor: palette.windowText property color disabledTextColor: disabledPalette.windowText property color highlightColor: palette.highlight property color highlightedTextColor: palette.highlightedText property color backgroundColor: palette.window + property color alternateBackgroundColor: Qt.darker(palette.window, 1.05) property color activeTextColor: palette.highlight property color linkColor: "#2980B9" property color visitedLinkColor: "#7F8C8D" property color hoverColor: palette.highlight property color focusColor: palette.highlight property color negativeTextColor: "#DA4453" property color neutralTextColor: "#F67400" property color positiveTextColor: "#27AE60" property color buttonTextColor: palette.buttonText property color buttonBackgroundColor: palette.button + property color buttonAlternateBackgroundColor: Qt.darker(palette.button, 1.05) property color buttonHoverColor: palette.highlight property color buttonFocusColor: palette.highlight property color viewTextColor: palette.text property color viewBackgroundColor: palette.base + property color viewAlternateBackgroundColor: palette.alternateBase property color viewHoverColor: palette.highlight property color viewFocusColor: palette.highlight property color selectionTextColor: palette.highlightedText property color selectionBackgroundColor: palette.highlight + property color selectionAlternateBackgroundColor: Qt.darker(palette.highlight, 1.05) property color selectionHoverColor: palette.highlight property color selectionFocusColor: palette.highlight property color tooltipTextColor: palette.base property color tooltipBackgroundColor: palette.text + property color tooltipAlternateBackgroundColor: Qt.darker(palette.text, 1.05) property color tooltipHoverColor: palette.highlight property color tooltipFocusColor: palette.highlight property color complementaryTextColor: palette.base property color complementaryBackgroundColor: palette.text + property color complementaryAlternateBackgroundColor: Qt.darker(palette.text, 1.05) property color complementaryHoverColor: palette.highlight property color complementaryFocusColor: palette.highlight property font defaultFont: fontMetrics.font property list children: [ TextMetrics { id: fontMetrics }, SystemPalette { id: palette colorGroup: SystemPalette.Active }, SystemPalette { id: disabledPalette colorGroup: SystemPalette.Disabled } ] function __propagateColorSet(object, context) {} function __propagateTextColor(object, color) {} function __propagateBackgroundColor(object, color) {} function __propagatePrimaryColor(object, color) {} function __propagateAccentColor(object, color) {} }