diff --git a/examples/galleryapp/CMakeLists.txt b/examples/galleryapp/CMakeLists.txt --- a/examples/galleryapp/CMakeLists.txt +++ b/examples/galleryapp/CMakeLists.txt @@ -43,4 +43,4 @@ install(TARGETS kirigami2gallery ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) include(${CMAKE_SOURCE_DIR}/KF5Kirigami2Macros.cmake) -kirigami_package_breeze_icons(ICONS applications-graphics view-list-icons folder-sync view-list-details configure document-edit dialog-cancel document-decrypt mail-reply-sender bookmarks folder media-record-symbolic) +kirigami_package_breeze_icons(ICONS applications-graphics view-list-icons folder-sync view-list-details configure document-edit dialog-cancel document-decrypt mail-reply-sender bookmarks folder media-record-symbolic add-placemark address-book-new-symbolic) diff --git a/examples/galleryapp/resources.qrc b/examples/galleryapp/resources.qrc --- a/examples/galleryapp/resources.qrc +++ b/examples/galleryapp/resources.qrc @@ -16,6 +16,9 @@ ../gallerydata/contents/ui/gallery/TabBarGallery.qml ../gallerydata/contents/ui/gallery/TextFieldGallery.qml ../gallerydata/contents/ui/gallery/ColorsGallery.qml + ../gallerydata/contents/ui/gallery/CardsLayoutGallery.qml + ../gallerydata/contents/ui/gallery/CardsListViewGallery.qml + ../gallerydata/contents/ui/gallery/CardsGridViewGallery.qml ../gallerydata/contents/ui/gallery/MetricsGallery.qml ../gallerydata/contents/ui/gallery/LayersGallery.qml ../gallerydata/contents/ui/gallery/FormLayoutGallery.qml diff --git a/examples/gallerydata/contents/ui/MainPage.qml b/examples/gallerydata/contents/ui/MainPage.qml --- a/examples/gallerydata/contents/ui/MainPage.qml +++ b/examples/gallerydata/contents/ui/MainPage.qml @@ -103,6 +103,18 @@ component: "FormLayout" } ListElement { + text: "Cards Layout" + component: "CardsLayout" + } + ListElement { + text: "List view of cards" + component: "CardsListView" + } + ListElement { + text: "Grid view of cards" + component: "CardsGridView" + } + ListElement { text: "Multiple Columns" component: "MultipleColumns" } diff --git a/examples/gallerydata/contents/ui/gallery/CardsGridViewGallery.qml b/examples/gallerydata/contents/ui/gallery/CardsGridViewGallery.qml new file mode 100644 --- /dev/null +++ b/examples/gallerydata/contents/ui/gallery/CardsGridViewGallery.qml @@ -0,0 +1,107 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ScrollablePage { + id: page + + title: "Grid view of Cards" + + actions.main: Kirigami.Action { + iconName: "documentinfo" + text: "Info" + checkable: true + onCheckedChanged: sheet.sheetOpen = checked; + shortcut: "Alt+I" + } + + Kirigami.OverlaySheet { + id: sheet + onSheetOpenChanged: page.actions.main.checked = sheetOpen + header: RowLayout { + Kirigami.Heading { + Layout.fillWidth: true + text: "CardsGridView" + } + Controls.ToolButton { + text: "HIG..." + enabled: false + onClicked: Qt.openUrlExternally("") + } + Controls.ToolButton { + text: "Source code..." + onClicked: Qt.openUrlExternally("https://cgit.kde.org/kirigami.git/tree/examples/gallerydata/contents/ui/gallery/CardsGridViewGallery.qml + ") + } + } + + Controls.Label { + property int implicitWidth: Kirigami.Units.gridUnit * 25 + wrapMode: Text.WordWrap + text: "The Kirigami types AbstractCard and Card are used to implement the popular Card pattern used on many mobile and web platforms that is used to display a collection of information or actions.\n Besides the Card components, Kirigami offers also 3 kinds of views and positioners to help to present cards with beautiful and responsive layouts.\n\nIn this page, CardsGridView shows an example on how to put cards in a grid view, generated by a Qt model.\nThe behavior is same as CardsLayout, and it allows cards to be put in one or two columns depending from the available width.\nCardsGridView has the limitation that every Card must have the same exact height, so cellHeight must be manually set to a value in which the content fits for every item.\nIf possible use cards only when you don't need to instantiate that many and use CardsLayout intead." + } + } + + Component.onCompleted: { + for (var i = 0; i < 50; ++i) { + mainModel.append({"title": "Item " + i, + "image": "../banner.jpg", + "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan. Nunc vestibulum placerat bibendum.", + "actions": [{text: "Action 1", icon: "add-placemark"}, + {text: "Action 2", icon: "address-book-new-symbolic"}] + }) + } + } + Kirigami.CardsGridView { + id: view + model: ListModel { + id: mainModel + } + +//property Component delegate + delegate:Kirigami.Card { + id: card + banner { + title: model.title + imageSource: model.image + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: model.text + } + //HACK: this instantiator hack is just for demonstration purposes, normally the actions objects should be embedded as a role of a QAbstractItemModel, either as QActions or just QObjects with the proper properties and signals (the new qqc2 Action should ideally become a public c++ type) + property var actionsModel: model.actions + Instantiator { + model: actionsModel + delegate: Kirigami.Action { + text: model.text + icon.name: model.icon + onTriggered: showPassiveNotificaton(model.text + " triggered") + } + onObjectAdded: { + card.actions.push(object) + } + } + } + } +} diff --git a/examples/gallerydata/contents/ui/gallery/CardsLayoutGallery.qml b/examples/gallerydata/contents/ui/gallery/CardsLayoutGallery.qml new file mode 100644 --- /dev/null +++ b/examples/gallerydata/contents/ui/gallery/CardsLayoutGallery.qml @@ -0,0 +1,252 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ScrollablePage { + id: page + + title: "Cards Layout" + + actions.main: Kirigami.Action { + iconName: "documentinfo" + text: "Info" + checkable: true + onCheckedChanged: sheet.sheetOpen = checked; + shortcut: "Alt+I" + } + + Kirigami.OverlaySheet { + id: sheet + onSheetOpenChanged: page.actions.main.checked = sheetOpen + header: RowLayout { + Kirigami.Heading { + Layout.fillWidth: true + text: "CardsLayout" + } + Controls.ToolButton { + text: "HIG..." + enabled: false + onClicked: Qt.openUrlExternally("") + } + Controls.ToolButton { + text: "Source code..." + onClicked: Qt.openUrlExternally("https://cgit.kde.org/kirigami.git/tree/examples/gallerydata/contents/ui/gallery/CardsLayoutGallery.qml + ") + } + } + + Controls.Label { + property int implicitWidth: Kirigami.Units.gridUnit * 25 + wrapMode: Text.WordWrap + text: "The Kirigami types AbstractCard and Card are used to implement the popular Card pattern used on many mobile and web platforms that is used to display a collection of information or actions.\n Besides the Card components, Kirigami offers also 3 kinds of views and positioners to help to present cards with beautiful and responsive layouts.\n\nIn this page, CardsLayout is presented, which should be used when the cards are not instantiated by a model or by a model which has always very few items (In the case of a big model CardsListView or CardsGridview should be used instead). They are presented as a grid of two columns which will remain centered if the application is really wide, or become a single column if there is not enough space for two columns, such as a mobile phone screen.\nA CardsLayout should always be contained within a ColumnLayout." + } + } + + ColumnLayout { + Kirigami.CardsLayout { + id: layout + Kirigami.AbstractCard { + Layout.fillWidth: true + Layout.fillHeight: true + header: Kirigami.Heading { + text: "AbstractCard" + level: 2 + } + contentItem: Controls.Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: "An AbstractCard is the simplest form of card. It's just a rectangle with a shadow, which can contain any Item in it. It can also have items assigned to the Header or Footer properties. In this case a Kirigami.Heading is its header and a Label with WordWrap is the contentItem." + } + } + + Kirigami.AbstractCard { + Layout.fillWidth: true + showClickFeedback: true + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: "This is an AbstractCard with a Label with WordWrap in it and nothing else, it's the simplest form Cards can be found in.\nAn AbstractCard can be clicked itself, with the usual onClicked signal handler and the showClickFeedback property can be used if the click should show any kind of visual feedback. It is recommended to set it to true if you plan to make the card reactive on any kind of click." + } + onClicked: showPassiveNotification("Card clicked") + } + + Kirigami.Card { + Layout.fillWidth: true + actions: [ + Kirigami.Action { + text: "Action1" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action2" + icon.name: "address-book-new-symbolic" + } + ] + banner { + imageSource: "../banner.jpg" + title: "Card" + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: "This is an instance of the Card type: it can optionally have an image, a title and an icon assigned to its banner group property, one or all of the properties together. A Card can also have Actions that will appear in the footer." + } + } + + + Kirigami.Card { + Layout.fillWidth: true + actions: [ + Kirigami.Action { + text: "Action1" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action2" + icon.name: "address-book-new-symbolic" + }, + Kirigami.Action { + text: "Action3" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action4" + icon.name: "address-book-new-symbolic" + }, + Kirigami.Action { + text: "Action5" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action6" + icon.name: "address-book-new-symbolic" + } + ] + banner { + imageSource: "../banner.jpg" + title: "Title Alignment" + titleAlignment: Qt.AlignLeft | Qt.AlignBottom + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: "The title can be aligned to all corners or centered with a combination of Qt.Alignment flags.\n When there are too many actions, they go in an overflow menu." + } + } + + Kirigami.Card { + Layout.fillWidth: true + actions: [ + Kirigami.Action { + text: "Action1" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action2" + icon.name: "address-book-new-symbolic" + }, + Kirigami.Action { + text: "Action3" + icon.name: "add-placemark" + } + ] + banner { + iconSource: "applications-graphics" + title: "Title Alignment" + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: "The Banner can contain only the title and/or an icon, even if there is no banner image." + } + } + + Kirigami.Card { + Layout.fillWidth: true + banner.imageSource: "../banner.jpg" + + header: Rectangle { + color: Qt.rgba(0,0,0,0.3) + implicitWidth: headerLayout.implicitWidth + implicitHeight: headerLayout.implicitHeight - avatarIcon.height/2 + ColumnLayout { + id: headerLayout + anchors { + left: parent.left + right: parent.right + } + Controls.Label { + Layout.fillWidth: true + padding: Kirigami.Units.largeSpacing + + color: "white" + wrapMode: Text.WordWrap + text: "It's possible to have custom contents overlapping the image, for cases where a more personalised design is needed." + } + Rectangle { + id: avatarIcon + color: "steelblue" + radius: width + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Kirigami.Units.iconSizes.huge + Layout.preferredHeight: Kirigami.Units.iconSizes.huge + } + } + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + topPadding: avatarIcon.height/2 + text: "It's possible to customize the look and feel for Cards too, if the no padding behavior for headers is needed. This is usually discouraged in order to have greater consistency, but in some cases the design requires a more fancy layout, as shown in this example of a Card. If a custom header is used, the title and icon in the banner property shouldn't be used. If a custom footer is used (which is discouraged), actions shouldn't be used." + } + footer: RowLayout { + Controls.Label { + Layout.fillWidth: true + text: "Custom footer" + } + Controls.Button { + text: "Ok" + } + } + } + + Kirigami.Card { + Layout.fillWidth: true + headerOrientation: Qt.Horizontal + actions: [ + Kirigami.Action { + text: "Action1" + icon.name: "add-placemark" + }, + Kirigami.Action { + text: "Action2" + icon.name: "address-book-new-symbolic" + } + ] + banner { + imageSource: "../banner.jpg" + title: "Title" + } + contentItem: Controls.Label { + wrapMode: Text.WordWrap + text: "A card can optionally have horizontal orientation.\n In this case will be wider than tall, so is fit to be used also in a ColumnLayout.\nIf you need to put it in a CardsLayout, it will have by default a columnSpan of 2 (which can be overridden)." + } + } + } + } +} diff --git a/examples/gallerydata/contents/ui/gallery/CardsListViewGallery.qml b/examples/gallerydata/contents/ui/gallery/CardsListViewGallery.qml new file mode 100644 --- /dev/null +++ b/examples/gallerydata/contents/ui/gallery/CardsListViewGallery.qml @@ -0,0 +1,116 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ScrollablePage { + id: page + + title: "List view of simple cards" + + actions.main: Kirigami.Action { + iconName: "documentinfo" + text: "Info" + checkable: true + onCheckedChanged: sheet.sheetOpen = checked; + shortcut: "Alt+I" + } + + Kirigami.OverlaySheet { + id: sheet + onSheetOpenChanged: page.actions.main.checked = sheetOpen + header: RowLayout { + Kirigami.Heading { + Layout.fillWidth: true + text: "CardsGridView" + } + Controls.ToolButton { + text: "HIG..." + enabled: false + onClicked: Qt.openUrlExternally("") + } + Controls.ToolButton { + text: "Source code..." + onClicked: Qt.openUrlExternally("https://cgit.kde.org/kirigami.git/tree/examples/gallerydata/contents/ui/gallery/CardsListViewGallery.qml + ") + } + } + + Controls.Label { + property int implicitWidth: Kirigami.Units.gridUnit * 25 + wrapMode: Text.WordWrap + text: "The Kirigami types AbstractCard and Card are used to implement the popular Card pattern used on many mobile and web platforms that is used to display a collection of information or actions.\n Besides the Card components, Kirigami offers also 3 kinds of views and positioners to help to present cards with beautiful and responsive layouts.\n\nIn this page, CardsListView is used to do a list view of AbstractCard subclasses with a custom layout inside.\n CardsListView should be used only with cards which can look good at any horizontal size, so it is recommended to use directly AbstractCard with an appropriate layout inside, because they are stretching for the whole list width.\nTherefore is discouraged to use it with the Card type, unless it has Horizontal as headerOrientation.\n The choice between using this view with AbstractCard or a normal ListView with AbstractListItem/BasicListItem is purely a choice based on aestetics alone." + } + } + + Kirigami.CardsListView { + model: 100 + + delegate: Kirigami.AbstractCard { + + //NOTE: never put a Layout as contentItem as it will cause binding loops + //SEE: https://bugreports.qt.io/browse/QTBUG-66826 + contentItem: Item { + implicitWidth: delegateLayout.implicitWidth + implicitHeight: delegateLayout.implicitHeight + GridLayout { + id: delegateLayout + anchors { + left: parent.left + top: parent.top + right: parent.right + //IMPORTANT: never put the bottom margin + } + rowSpacing: Kirigami.Units.largeSpacing + columnSpacing: Kirigami.Units.largeSpacing + columns: width > Kirigami.Units.gridUnit * 20 ? 4 : 2 + Kirigami.Icon { + source: "applications-graphics" + Layout.fillHeight: true + Layout.maximumHeight: Kirigami.Units.iconSizes.huge + Layout.preferredWidth: height + } + ColumnLayout { + Kirigami.Heading { + level: 2 + text: "Product "+ modelData + } + Kirigami.Separator { + Layout.fillWidth: true + } + Controls.Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan." + } + } + Controls.Button { + Layout.alignment: Qt.AlignRight|Qt.AlignVCenter + Layout.columnSpan: 2 + text: "Install" + onClicked: showPassiveNotification("Install for Product " + modelData + " clicked"); + } + } + } + } + } +} diff --git a/kirigami.qrc b/kirigami.qrc --- a/kirigami.qrc +++ b/kirigami.qrc @@ -7,6 +7,7 @@ src/controls/PageRow.qml src/controls/AbstractListItem.qml src/controls/Theme.qml + src/controls/Card.qml src/controls/ToolBarApplicationHeader.qml src/controls/private/PrivateActionToolButton.qml src/controls/private/RefreshableScrollView.qml diff --git a/src/controls/AbstractCard.qml b/src/controls/AbstractCard.qml new file mode 100644 --- /dev/null +++ b/src/controls/AbstractCard.qml @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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.6 +import QtGraphicalEffects 1.0 +import org.kde.kirigami 2.3 as Kirigami +import "templates" as T + +/** + * A AbstractCard is the base for cards. A Card is a visual object that serves + * as an entry point for more detailed information. An abstractCard is empty, + * providing just the look and the base properties and signals for an ItemDelegate. + * It can be filled with any custom layout of items, its content is organized + * in 3 properties: header, contentItem and footer. + * Use this only when you need particular custom contents, for a standard layout + * for cards, use the Card component. + * + * @see Card + * @inherits T.AbstractCard + * @since 2.4 + */ +T.AbstractCard { + id: root + + background: Rectangle { + color: Kirigami.Theme.backgroundColor + Rectangle { + anchors.fill: parent + color: Kirigami.Theme.highlightColor + opacity: { + if (root.showClickFeedback) { + return root.down ? 0.3 : (root.hovered ? 0.1 : 0); + } else { + return 0; + } + } + Behavior on opacity { + OpacityAnimator { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + } + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 0 + verticalOffset: 1 + radius: 12 + samples: 32 + color: Qt.rgba(0, 0, 0, 0.5) + } + } +} diff --git a/src/controls/Card.qml b/src/controls/Card.qml new file mode 100644 --- /dev/null +++ b/src/controls/Card.qml @@ -0,0 +1,182 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.0 as Controls +import org.kde.kirigami 2.3 as Kirigami +import "private" + +/** + * This is the standard layout of a Card. + * It is recomended to use this class when the concept of Cards is needed + * in the application. + * This Card has default items as header and footer. The header is an + * image that can contain an optional title and icon, accessible via the + * banner grouped property. + * The footer will show a series of toolbuttons (and eventual overflow menu) + * represewnting the actions list accessible with the list property actions. + * It is possible even tough is discouraged to override the footer: + * in this case the actions property shouldn't be used. + * + * @inherits AbstractCard + * @since 2.4 + */ +AbstractCard { + id: root + + /** + * actions: list + * if the card should provide clickable actions, put them in this property, + * they will be put in the footer as a list of ToolButtons plus an optional + * overflow menu, when not all of them will fit in the available Card width. + */ + property list actions + + /** + * banner: grouped + * Gropuped property to control the banner image present in the header, it + * has the following sub properties: + * * url imageSource: the source for the image, it understands any url + * valid for an Image component + * * string title: the title for the banner, shown as contrasting + * text over the image + * * Qt.Alignment titleAlignment: the alignment of the title inside the image, + * a combination of flags is supported + * (default: Qt.AlignTop | Qt.AlignLeft) + * * string iconSource: the optional icon to put in the banner: + * it can be either a freedesktop-compatible icon name (recommended) + * or any url supported by Image + * * Image.FillMode fillMode: see the fillMode property of the Image component (default: Image.PreserveAspectCrop) + */ + readonly property alias banner: bannerGroup + + header: BannerImage { + id: bannerImage + BannerGroup { + id: bannerGroup + } + anchors.leftMargin: -root.leftPadding + anchors.topMargin: -root.topPadding + anchors.rightMargin: root.headerOrientation == Qt.Vertical ? -root.rightPadding : 0 + anchors.bottomMargin: root.headerOrientation == Qt.Horizontal ? -root.bottomPadding : 0 + title: bannerGroup.title + source: bannerGroup.imageSource + titleAlignment: bannerGroup.titleAlignment + titleIcon: bannerGroup.iconSource + fillMode: bannerGroup.fillMode + height: Layout.preferredHeight + + } + + onHeaderChanged: { + if (!header) { + return; + } + + header.anchors.leftMargin = Qt.binding(function() {return -root.leftPadding}); + header.anchors.topMargin = Qt.binding(function() {return -root.topPadding}); + header.anchors.rightMargin = Qt.binding(function() {return root.headerOrientation == Qt.Vertical ? -root.rightPadding : 0}); + header.anchors.bottomMargin = Qt.binding(function() {return root.headerOrientation == Qt.Horizontal ? -root.bottomPadding : 0}); + } + + footer: RowLayout { + id: actionsLayout + property var overflowSet: [] + visible: root.footer == actionsLayout + + Repeater { + model: root.actions + delegate: PrivateActionToolButton { + id: actionDelegate + property bool fits: { + var minX = 0; + for (var i = 0; i < index; ++i) { + if (actionsLayout.children[i].visible) { + minX += actionsLayout.children[i].implicitWidth; + } + } + return minX + implicitWidth < actionsLayout.width - moreButton.width; + } + visible: modelData.visible && fits + Layout.fillWidth: true + Layout.minimumWidth: implicitWidth + kirigamiAction: modelData + onFitsChanged: updateOverflowSet() + function updateOverflowSet() { + var index = actionsLayout.overflowSet.findIndex(function(act){ + return act == modelData}); + + if ((fits || !modelData.visible) && index > -1) { + actionsLayout.overflowSet.splice(index, 1); + } else if (!fits && modelData.visible && index == -1) { + actionsLayout.overflowSet.push(modelData); + } + actionsLayout.overflowSetChanged(); + } + Connections { + target: modelData + onVisibleChanged: actionDelegate.updateOverflowSet(); + } + Component.onCompleted: { + actionDelegate.updateOverflowSet(); + } + } + } + Controls.ToolButton { + id: moreButton + + Icon { + anchors.fill: parent + source: "overflow-menu" + anchors.margins: 4 + } + Layout.alignment: Qt.AlignRight + //checkable: true + checked: menu.visible + visible: actionsLayout.overflowSet.length > 0; + onClicked: menu.visible ? menu.close() : menu.open() + + Controls.Menu { + id: menu + y: -height + x: -width + moreButton.width + + Repeater { + model: root.actions + delegate: BasicListItem { + text: modelData ? modelData.text : "" + icon: modelData.icon + checkable: modelData.checkable + checked: modelData.checked + onClicked: { + modelData.trigger(); + menu.visible = false; + } + separatorVisible: false + backgroundColor: "transparent" + visible: actionsLayout.overflowSet.findIndex(function(act) { + return act == modelData}) > -1 && modelData.visible + enabled: modelData.enabled + } + } + } + } + } +} diff --git a/src/controls/CardsGridView.qml b/src/controls/CardsGridView.qml new file mode 100644 --- /dev/null +++ b/src/controls/CardsGridView.qml @@ -0,0 +1,55 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.3 as Kirigami + +/** + * CardsGridView is used to display a grid of Cards generated from any model. + * The behavior is same as CardsLayout, and it allowes cards to be put in one or two + * columns depending from the available width. + * GridView has the limitation that every Card must have the same exact height, + * so cellHeight must be manually set to a value in which the content fits + * for every item. + * If possible use cards only when you don't need to instantiate a lot + * and use CardsLayout intead. + * @inherits GridView + * @see CardsLayout + * @since 2.4 + */ +GridView { + id: root + + /** + * maximumColumnWidth: int + * The maximum width the columns may have. the cards will never + * get wider than this size, when the GridView is wider than + * maximumColumnWidth, it will switch from one to two columns. + * If the default needs to be overridden for some reason, + * it is advised to express this unit as a multiple + * of Kirigami.Units.gridUnit + */ + property int maximumColumnWidth: Kirigami.Units.gridUnit * 25 + cellWidth: width > maximumColumnWidth ? width/2 : width + cellHeight: Math.max(Kirigami.Units.gridUnit * 15, Math.min(cellWidth, maximumColumnWidth) / 1.2) + + topMargin: Kirigami.Units.largeSpacing * 2 +} diff --git a/src/controls/CardsLayout.qml b/src/controls/CardsLayout.qml new file mode 100644 --- /dev/null +++ b/src/controls/CardsLayout.qml @@ -0,0 +1,64 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami + +/** + * A GridLayout optimized for showing one or two columns of cards, + * depending on the available space. + * It Should be used when the cards are not instantiated by a model or by a + * model which has always very few items (In the case of a big model + * CardsListView or CardsGridview should be used instead). + * They are presented as a grid of two columns which will remain + * centered if the application is really wide, or become a single + * column if there is not enough space for two columns, + * such as a mobile phone screen. + * A CardsLayout should always be contained within a ColumnLayout. + * @inherits GridLayout + * @since 2.4 + */ +GridLayout { + /** + * maximumColumnWidth: int + * The maximum width the columns may have. the cards will never + * get wider than this size, when the GridLayout is wider than + * maximumColumnWidth, it will switch from one to two columns. + * If the default needs to be overridden for some reason, + * it is advised to express this unit as a multiple + * of Kirigami.Units.gridUnit + */ + property int maximumColumnWidth: Kirigami.Units.gridUnit * 25 + + columns: width > maximumColumnWidth ? 2 : 1 + rowSpacing: Kirigami.Units.largeSpacing * 2 + columnSpacing: Kirigami.Units.largeSpacing * 2 + + Layout.maximumWidth: Math.floor(maximumColumnWidth * 2) + Layout.alignment: Qt.AlignHCenter + + Component.onCompleted: childrenChanged() + onChildrenChanged: { + for (var i = 0; i < children.length; ++i) { + children[i].Layout.fillHeight = true; + } + } +} diff --git a/src/controls/CardsListView.qml b/src/controls/CardsListView.qml new file mode 100644 --- /dev/null +++ b/src/controls/CardsListView.qml @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Controls 2.0 as Controls +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.3 as Kirigami + +/** + * CardsListView is a ListView which can have AbstractCard as its delegete: it will + * automatically assign the proper spacings and margins around the cards adhering + * to the design guidelines. + * CardsListView should be used only with cards which can look good at any + * horizontal size, so It is recommended to use directly AbstractCard with an + * appropriate layout inside, because they are stretching for the whole list width. + * Therefore is discouraged to use it with the Card type, unless it has + * Horizontal as headerOrientation. + * The choice between using this view with AbstractCard or a normal ListView + * with AbstractListItem/BasicListItem is purely a choice based on aestetics alone. + * It is discouraged to tweak the properties of this ListView. + * @inherits ListView + * @since 2.4 + */ +ListView { + id: root + spacing: Kirigami.Units.largeSpacing * 2 + topMargin: headerPositioning != ListView.InlineHeader ? spacing : 0 + + headerPositioning: ListView.OverlayHeader + + onContentHeightChanged: { + var item = contentItem.children[0]; + if (item && !item.hasOwnProperty("header") && !item.hasOwnProperty("_contentItem")) { + print("Warning: only AbstractCard items are supported in CardsListView") + } + } +} diff --git a/src/controls/GlobalDrawer.qml b/src/controls/GlobalDrawer.qml --- a/src/controls/GlobalDrawer.qml +++ b/src/controls/GlobalDrawer.qml @@ -71,14 +71,14 @@ * title: string * A title to be displayed on top of the drawer */ - property alias title: heading.text + property alias title: bannerImage.title /** * icon: var * An icon to be displayed alongside the title. * It can be a QIcon, a fdo-compatible icon name, or any url understood by Image */ - property alias titleIcon: headingIcon.source + property alias titleIcon: bannerImage.titleIcon /** * bannerImageSource: string @@ -224,29 +224,16 @@ spacing: 0 height: Math.max(root.height, Layout.minimumHeight) - Image { + BannerImage { id: bannerImage Layout.fillWidth: true - Layout.preferredWidth: title.implicitWidth - Layout.preferredHeight: bannerImageSource != "" ? 10 * Units.gridUnit : Layout.minimumHeight - Layout.minimumHeight: title.height > 0 ? title.height + Units.smallSpacing * 2 : 0 - + fillMode: Image.PreserveAspectCrop MouseArea { anchors.fill: parent onClicked: root.bannerClicked() } - - fillMode: Image.PreserveAspectCrop - asynchronous: true - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - EdgeShadow { edge: Qt.BottomEdge visible: bannerImageSource != "" @@ -256,54 +243,6 @@ bottom: parent.top } } - LinearGradient { - anchors { - left: parent.left - right: parent.right - top: parent.top - } - visible: bannerImageSource != "" && root.title != "" - height: title.height * 1.3 - start: Qt.point(0, 0) - end: Qt.point(0, height) - gradient: Gradient { - GradientStop { - position: 0.0 - color: Qt.rgba(0, 0, 0, 0.8) - } - GradientStop { - position: 1.0 - color: "transparent" - } - } - } - - RowLayout { - id: title - anchors { - left: parent.left - top: parent.top - margins: Units.smallSpacing * 2 - } - Icon { - id: headingIcon - Layout.minimumWidth: Units.iconSizes.large - Layout.minimumHeight: width - visible: valid - isMask: false - //TODO: find a better way to control selective coloring on Android - enabled: !Settings.isMobile - } - Heading { - id: heading - Layout.fillWidth: true - Layout.rightMargin: heading.height - visible: text.length > 0 - level: 1 - color: bannerImageSource != "" ? "white" : Theme.textColor - elide: Text.ElideRight - } - } } ColumnLayout { diff --git a/src/controls/Heading.qml b/src/controls/Heading.qml --- a/src/controls/Heading.qml +++ b/src/controls/Heading.qml @@ -63,7 +63,6 @@ */ property int step: 2 - lineHeight: 1.2 font.pointSize: headerPointSize(level) font.weight: level <= 4 ? Font.Light : Font.Normal wrapMode: Text.WordWrap diff --git a/src/controls/Page.qml b/src/controls/Page.qml --- a/src/controls/Page.qml +++ b/src/controls/Page.qml @@ -225,8 +225,6 @@ */ signal backRequested(var event); - anchors.topMargin: (applicationWindow() && !applicationWindow().wideScreen && Kirigami.Settings.isMobile && applicationWindow().controlsVisible && applicationWindow().header ? applicationWindow().header.preferredHeight : 0) - //NOTE: This exists just because control instances require it contentItem: Item { onChildrenChanged: { diff --git a/src/controls/ScrollablePage.qml b/src/controls/ScrollablePage.qml --- a/src/controls/ScrollablePage.qml +++ b/src/controls/ScrollablePage.qml @@ -121,7 +121,7 @@ //child of root as it shouldn't have margins parent: root page: root - topPadding: (applicationWindow() && applicationWindow().header ? applicationWindow().header.preferredHeight : 0) + (contentItem == flickableItem ? 0 : root.topPadding) + topPadding: root.topPadding leftPadding: root.leftPadding rightPadding: root.rightPadding bottomPadding: contentItem == flickableItem ? 0 : root.bottomPadding diff --git a/src/controls/Units.qml b/src/controls/Units.qml --- a/src/controls/Units.qml +++ b/src/controls/Units.qml @@ -77,7 +77,7 @@ * the size of the default font as rendered on the screen, so it takes user-configured font * size and DPI into account. */ - property int largeSpacing: gridUnit + property int largeSpacing: Math.floor(gridUnit/2) /** * The ratio between physical and device-independent pixels. This value does not depend on the \ diff --git a/src/controls/private/BannerGroup.qml b/src/controls/private/BannerGroup.qml new file mode 100644 --- /dev/null +++ b/src/controls/private/BannerGroup.qml @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.6 + +QtObject { + property url imageSource + property string title + property int titleAlignment: Qt.AlignTop | Qt.AlignLeft + property string iconSource + property int fillMode: Image.PreserveAspectCrop +} diff --git a/src/controls/private/BannerImage.qml b/src/controls/private/BannerImage.qml new file mode 100644 --- /dev/null +++ b/src/controls/private/BannerImage.qml @@ -0,0 +1,109 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 +import org.kde.kirigami 2.3 as Kirigami + +Image { + id: root + + /** + * title: string + * A title to be displayed on top of the image + */ + property alias title: heading.text + + /** + * icon: var + * An icon to be displayed alongside the title. + * It can be a QIcon, a fdo-compatible icon name, or any url understood by Image + */ + property alias titleIcon: headingIcon.source + + /** + * titleAlignment: Qt.Alignment + */ + property int titleAlignment: Qt.AlignTop | Qt.AlignLeft + + Layout.fillWidth: true + + Layout.preferredWidth: title.implicitWidth + Layout.preferredHeight: source != "" ? width/(sourceSize.width / sourceSize.height) : Layout.minimumHeight + Layout.minimumHeight: title.height > 0 ? title.height + Kirigami.Units.smallSpacing * 2 : 0 + + fillMode: Image.PreserveAspectFit + asynchronous: true + + LinearGradient { + anchors { + left: parent.left + right: parent.right + top: (root.titleAlignment & Qt.AlignTop) ? parent.top : undefined + bottom: (root.titleAlignment & Qt.AlignBottom) ? parent.bottom : undefined + } + visible: root.source != "" && root.title != "" && ((root.titleAlignment & Qt.AlignTop) || (root.titleAlignment & Qt.AlignBottom)) + height: title.height * 2 + start: Qt.point(0, 0) + end: Qt.point(0, height) + gradient: Gradient { + GradientStop { + position: (root.titleAlignment & Qt.AlignTop) ? 0.0 : 1.0 + color: Qt.rgba(0, 0, 0, 0.8) + } + GradientStop { + position: (root.titleAlignment & Qt.AlignTop) ? 1.0 : 0.0 + color: "transparent" + } + } + } + + RowLayout { + id: title + anchors { + left: root.titleAlignment & Qt.AlignLeft ? parent.left : undefined + top: root.titleAlignment & Qt.AlignTop ? parent.top : undefined + right: root.titleAlignment & Qt.AlignRight ? parent.right : undefined + bottom: root.titleAlignment & Qt.AlignBottom ? parent.bottom : undefined + horizontalCenter: root.titleAlignment & Qt.AlignHCenter ? parent.horizontalCenter : undefined + verticalCenter: root.titleAlignment & Qt.AlignVCenter ? parent.verticalCenter : undefined + + margins: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing + } + Kirigami.Icon { + id: headingIcon + Layout.minimumWidth: Kirigami.Units.iconSizes.large + Layout.minimumHeight: width + visible: valid + isMask: false + //TODO: find a better way to control selective coloring on Android + enabled: !Kirigami.Settings.isMobile + } + Kirigami.Heading { + id: heading + Layout.fillWidth: true + Layout.rightMargin: heading.height + visible: text.length > 0 + level: 1 + color: source != "" ? "white" : Kirigami.Theme.textColor + elide: Text.ElideRight + } + } +} diff --git a/src/controls/private/RefreshableScrollView.qml b/src/controls/private/RefreshableScrollView.qml --- a/src/controls/private/RefreshableScrollView.qml +++ b/src/controls/private/RefreshableScrollView.qml @@ -205,13 +205,6 @@ applicationWindow().reachableMode = !applicationWindow().reachableMode; } } - Binding { - target: root.flickableItem - property: "topMargin" - value: !Settings.isMobile || applicationWindow().wideScreen - ? (root.refreshing ? busyIndicatorFrame.height : 0) - : Math.max(Math.max(root.topPadding - busyIndicatorFrame.headerItemHeight, 0) + (root.refreshing ? busyIndicatorFrame.height : 0), (applicationWindow().header ? applicationWindow().header.height : 0)) - } Binding { target: root.flickableItem diff --git a/src/controls/templates/AbstractApplicationHeader.qml b/src/controls/templates/AbstractApplicationHeader.qml --- a/src/controls/templates/AbstractApplicationHeader.qml +++ b/src/controls/templates/AbstractApplicationHeader.qml @@ -53,15 +53,7 @@ left: parent.left right: parent.right } - height: { - if (!__appWindow.controlsVisible) { - return 1; - } else if (__appWindow.wideScreen || !Settings.isMobile) { - return preferredHeight; - } else { - return 1; - } - } + height: preferredHeight /** * background: Item @@ -76,85 +68,81 @@ background.anchors.fill = headerItem; } - opacity: height > 0 && -translateTransform.y <= height ? 1 : 0 + opacity: height > 0 ? 1 : 0 Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } - transform: Translate { - id: translateTransform - y: { - if (__appWindow === undefined) { - return 0; - } - if (!__appWindow.controlsVisible) { - return -headerItem.height - Units.smallSpacing; - } else { - return 0; - } - } - Behavior on y { - NumberAnimation { - duration: Units.longDuration - easing.type: translateTransform.y < 0 ? Easing.OutQuad : Easing.InQuad - } + Behavior on height { + enabled: __appWindow.pageStack.currentItem && __appWindow.pageStack.currentItem.flickable && !__appWindow.pageStack.currentItem.flickable.moving + NumberAnimation { + duration: Units.longDuration + easing.type: Easing.InOutQuad } } + Connections { + target: __appWindow + onControlsVisibleChanged: root.height = __appWindow.controlsVisible ? root.preferredHeight : 0; + } + Item { id: headerItem + property real computedRootHeight: root.preferredHeight anchors { left: parent.left right: parent.right + bottom: parent.bottom } height: __appWindow.reachableMode && __appWindow.reachableModeEnabled ? root.maximumHeight : root.preferredHeight - function updatePageHeader() { - if (!__appWindow || !__appWindow.pageStack || !__appWindow.pageStack.currentItem || !__appWindow.pageStack.currentItem.header || !__appWindow.pageStack.currentItem.flickable) { - return; - } - - if (__appWindow.wideScreen || !Settings.isMobile) { - __appWindow.pageStack.currentItem.header.y = 0; - } else { - __appWindow.pageStack.currentItem.header.y = headerItem.height + headerItem.y -1; - } - } - onYChanged: updatePageHeader() - onHeightChanged: updatePageHeader() - Connections { id: headerSlideConnection target: __appWindow.pageStack.currentItem ? __appWindow.pageStack.currentItem.flickable : null property int oldContentY onContentYChanged: { - if (!__appWindow.pageStack.currentItem) { - return; - } - if (__appWindow.pageStack.currentItem.flickable.atYBeginning || + if (!Settings.isMobile || + !__appWindow.controlsVisible || + !__appWindow.pageStack.currentItem || + __appWindow.pageStack.currentItem.flickable.atYBeginning || __appWindow.pageStack.currentItem.flickable.atYEnd) { return; - } + //if moves but not dragging, just update oldContentY + } else if (!__appWindow.pageStack.currentItem.flickable.dragging) { + oldContentY = __appWindow.pageStack.currentItem.flickable.contentY; + return; + } + if (__appWindow.wideScreen || !Settings.isMobile) { - headerItem.y = 0; + root.height = root.preferredHeight; } else { - headerItem.y = Math.max(root.minimumHeight - root.preferredHeight, Math.min(0, headerItem.y + oldContentY - __appWindow.pageStack.currentItem.flickable.contentY)); - - oldContentY = __appWindow.pageStack.currentItem.flickable.contentY; + var oldHeight = root.height; + + root.height = Math.max(root.minimumHeight, + Math.min(root.preferredHeight, + root.height + oldContentY - __appWindow.pageStack.currentItem.flickable.contentY)); + + //if the height is changed, use that to simulate scroll + if (oldHeight != height) { + __appWindow.pageStack.currentItem.flickable.contentY = oldContentY; + } else { + oldContentY = __appWindow.pageStack.currentItem.flickable.contentY; + } } } onMovementEnded: { - if (headerItem.y > root.preferredHeight) { - //if don't change the position if more then preferredSize is shown - } else if (headerItem.y < -(root.preferredHeight - root.minimumHeight)/2 ) { - headerItem.y = root.minimumHeight - root.preferredHeight; + if (__appWindow.wideScreen || !Settings.isMobile) { + return; + } + if (root.height > (root.preferredHeight - root.minimumHeight)/2 ) { + root.height = root.preferredHeight; } else { - headerItem.y = 0; + root.height = root.minimumHeight; } } } @@ -169,8 +157,7 @@ } else { headerSlideConnection.oldContentY = 0; } - headerItem.y = 0; - headerItem.updatePageHeader() + root.height = root.preferredHeight; } } @@ -180,13 +167,6 @@ fill: parent } } - Behavior on y { - enabled: __appWindow.pageStack.currentItem && __appWindow.pageStack.currentItem.flickable && !__appWindow.pageStack.currentItem.flickable.moving - NumberAnimation { - duration: Units.longDuration - easing.type: Easing.InOutQuad - } - } } } diff --git a/src/controls/templates/AbstractCard.qml b/src/controls/templates/AbstractCard.qml new file mode 100644 --- /dev/null +++ b/src/controls/templates/AbstractCard.qml @@ -0,0 +1,185 @@ +/* + * Copyright 2018 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.6 +import QtQuick.Layouts 1.2 +import QtQuick.Templates 2.0 as T +import org.kde.kirigami 2.3 as Kirigami + +/** + * A AbstractCard is the base for cards. A Card is a visual object that serves + * as an entry point for more detailed information. An abstractCard is empty, + * providing just the look and the base properties and signals for an ItemDelegate. + * It can be filled with any custom layout of items, its content is organized + * in 3 properties: header, contentItem and footer. + * Use this only when you need particular custom contents, for a standard layout + * for cards, use the Card component. + * + * @see Card + * @inherits QtQuick.Templates.ItemDelegate + * @since 2.4 + */ +T.ItemDelegate { + id: root + + /** + * header: Item + * This item serves as header, it will be put either on top if headerOrientation + * is Qt.Vertical(default) or on the left if it's Qt.Horizontal + */ + property Item header + + /** + * headerOrientation: Qt.Orientation + * If Qt.Vertical the header will be positioned on top(default), + * if Qt.Horizontal will be positioned on the left (or right if an RTL layout is used) + */ + property int headerOrientation: Qt.Vertical + + /** + * footer: Item + * This item serves as footer, and it will be positioned at the bottom of the card. + */ + property Item footer + + /** + * showClickFeedback: bool + * if true, when clicking or tapping on the card area, the card will be colored + * to show a visual click feedback. + * Use this if you want to do an action in the onClicked signal handler of the card. + */ + property bool showClickFeedback: false + + implicitWidth: internal.completed + ? (internal.listView ? internal.listView.width - internal.listView.spacing * 2 : + (internal.gridView ? Math.min(internal.gridView.cellWidth, internal.gridView.maximumColumnWidth) - Kirigami.Units.largeSpacing*2 : Math.max(background.implicitWidth, mainLayout.implicitWidth) + leftPadding + rightPadding)) + : 0 + + implicitHeight: internal.completed && internal.gridView + ? internal.gridView.cellHeight - Kirigami.Units.largeSpacing*2 + : mainLayout.implicitHeight + topPadding + bottomPadding + + x: internal.listView ? internal.listView.spacing : (internal.gridView ? internal.gridView.cellWidth - width: 0) + + //in grid views align the cells in the middle + anchors.left: internal.gridView !== null ? parent.left : undefined + anchors.leftMargin: internal.gridView === null || internal.gridView.width <= internal.gridView.maximumColumnWidth ? 0 : (index % 2 === 0 ? Math.max(0, internal.gridView.cellWidth - internal.gridView.maximumColumnWidth) : internal.gridView.cellWidth) + + hoverEnabled: !Kirigami.Settings.isMobile && showClickFeedback + //if it's in a CardLayout, try to expand horizontal cards to both columns + Layout.columnSpan: headerOrientation == Qt.Horizontal && parent.hasOwnProperty("columns") ? parent.columns : 1 + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + + topPadding: Kirigami.Units.largeSpacing + leftPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + + GridLayout { + id: mainLayout + rowSpacing: root.topPadding + columnSpacing: root.leftPadding + anchors { + top: parent.top + left: parent.left + right: parent.right + leftMargin: root.leftPadding + topMargin: root.topPadding + rightMargin: root.rightPadding + //never anchor bottom, to not have binding loops + } + columns: headerOrientation == Qt.Vertical ? 1 : 2 + function preferredHeight(item) { + if (!item) { + return 0; + } + if (item.Layout.preferredHeight > 0) { + return item.Layout.preferredHeight; + } + return item.implicitHeight + } + Item { + id: headerParent + Layout.fillWidth: true + Layout.fillHeight: root.headerOrientation == Qt.Horizontal + Layout.rowSpan: root.headerOrientation == Qt.Vertical ? 1 : 2 + Layout.preferredWidth: header ? header.implicitWidth : 0 + Layout.preferredHeight: root.headerOrientation == Qt.Vertical ? mainLayout.preferredHeight(header) : -1 + } + Item { + id: contentItemParent + Layout.fillWidth: true + Layout.preferredWidth: contentItem ? contentItem.implicitWidth : 0 + Layout.preferredHeight: mainLayout.preferredHeight(contentItem) + } + Item { + id: footerParent + Layout.fillWidth: true + Layout.preferredWidth: footer ? footer.implicitWidth : 0 + Layout.preferredHeight: mainLayout.preferredHeight(footer) + } + } + QtObject { + id: internal + property bool completed: false + property ListView listView + property GridView gridView + } +//BEGIN signal handlers + onContentItemChanged: { + if (!contentItem) { + return; + } + + contentItem.parent = contentItemParent; + contentItem.anchors.fill = contentItemParent; + } + onHeaderChanged: { + if (!header) { + return; + } + + header.parent = headerParent; + header.anchors.fill = headerParent; + } + onFooterChanged: { + if (!footer) { + return; + } + + //make the footer always looking it's at the bottom of the card + footer.parent = footerParent; + footer.anchors.left = footerParent.left; + footer.anchors.top = footerParent.top; + footer.anchors.right = footerParent.right; + footer.anchors.topMargin = Qt.binding(function() {return (root.height - root.bottomPadding - root.topPadding) - (footerParent.y + footerParent.height)}); + } + Component.onCompleted: { + internal.listView = ListView.view; + //only consider gridviews which are CardsGridView + if (GridView.view && GridView.view.hasOwnProperty("maximumColumnWidth")) { + internal.gridView = GridView.view; + } + internal.completed = true; + contentItemChanged(); + } +//END signal handlers +} diff --git a/src/controls/templates/private/ScrollView.qml b/src/controls/templates/private/ScrollView.qml --- a/src/controls/templates/private/ScrollView.qml +++ b/src/controls/templates/private/ScrollView.qml @@ -46,18 +46,25 @@ drag.filterChildren: !Settings.isMobile onPressed: { + if (Settings.isMobile) { + return; + } mouse.accepted = false; flickableItem.interactive = true; } onReleased: { + if (Settings.isMobile) { + return; + } mouse.accepted = false; flickableItem.interactive = false; } onWheel: { - flickableItem.interactive = false; if (Settings.isMobile || flickableItem.contentHeight(uri, 2, 3, "FormData", "Cannot create objects of type FormData, use it as an attached poperty"); qmlRegisterUncreatableType(uri, 2, 3, "MnemonicData", "Cannot create objects of type MnemonicData, use it as an attached poperty"); + //2.4 + qmlRegisterType(componentUrl(QStringLiteral("AbstractCard.qml")), uri, 2, 4, "AbstractCard"); + qmlRegisterType(componentUrl(QStringLiteral("Card.qml")), uri, 2, 4, "Card"); + qmlRegisterType(componentUrl(QStringLiteral("CardsListView.qml")), uri, 2, 4, "CardsListView"); + qmlRegisterType(componentUrl(QStringLiteral("CardsGridView.qml")), uri, 2, 4, "CardsGridView"); + qmlRegisterType(componentUrl(QStringLiteral("CardsLayout.qml")), uri, 2, 4, "CardsLayout"); + qmlProtectModule(uri, 2); } diff --git a/src/styles/org.kde.desktop/Units.qml b/src/styles/org.kde.desktop/Units.qml --- a/src/styles/org.kde.desktop/Units.qml +++ b/src/styles/org.kde.desktop/Units.qml @@ -75,7 +75,7 @@ * the size of the default font as rendered on the screen, so it takes user-configured font * size and DPI into account. */ - property int largeSpacing: gridUnit + property int largeSpacing: Math.floor(gridUnit/2) /** * The ratio between physical and device-independent pixels. This value does not depend on the \