diff --git a/examples/gallerydata/contents/ui/ExampleApp.qml b/examples/gallerydata/contents/ui/ExampleApp.qml index 3a9a22b3..bd00af6b 100644 --- a/examples/gallerydata/contents/ui/ExampleApp.qml +++ b/examples/gallerydata/contents/ui/ExampleApp.qml @@ -1,158 +1,160 @@ /* * 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.1 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 as Kirigami import "gallery" Kirigami.ApplicationWindow { id: root //header: Kirigami.ApplicationHeader {} - +pageStack.globalToolBar.minimumHeight: 10 +pageStack.globalToolBar.preferredHeight: 40 +pageStack.globalToolBar.maximumHeight: 80 globalDrawer: Kirigami.GlobalDrawer { title: "Widget gallery" titleIcon: "applications-graphics" bannerImageSource: "banner.jpg" actions: [ Kirigami.Action { text: "Top Bar Style" iconName: "view-list-icons" Kirigami.Action { text: "Auto" onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.Auto checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.Auto } Kirigami.Action { text: "Breadcrumb" onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.Breadcrumb checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.Breadcrumb } Kirigami.Action { text: "TabBar" onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.TabBar checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.TabBar } Kirigami.Action { text: "Titles" onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.Titles checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.Titles } Kirigami.Action { text: "ToolBar" visible: !Kirigami.Settings.isMobile onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.ToolBar checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.ToolBar } Kirigami.Action { text: "None" onTriggered: root.pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.None checked: root.pageStack.globalToolBar.style == Kirigami.ApplicationHeaderStyle.None } }, Kirigami.Action { text: "Submenu 2" iconName: "folder-sync" Kirigami.Action { text: "Action 4" onTriggered: showPassiveNotification(text + " clicked") } Kirigami.Action { text: "Action 5" onTriggered: showPassiveNotification(text + " clicked") } }, Kirigami.Action { text: "Checkable" iconName: "go-next" checkable: true checked: false onTriggered: { showPassiveNotification("Action checked: " + checked) } }, Kirigami.Action { text: "Open A Page" iconName: "view-list-details" checkable: true //Need to do this, otherwise it breaks the bindings property bool current: pageStack.currentItem ? pageStack.currentItem.objectName == "settingsPage" : false onCurrentChanged: { checked = current; } onTriggered: { pageStack.push(settingsComponent); } }, Kirigami.Action { text: "Open A Layer" iconName: "configure" onTriggered: { pageStack.layers.push(Qt.resolvedUrl("gallery/LayersGallery.qml")); } } ] Controls.CheckBox { checked: true text: "Option 1" } Controls.CheckBox { text: "Option 2" } Controls.CheckBox { text: "Option 3" } Controls.Slider { Layout.fillWidth: true value: 0.5 } } contextDrawer: Kirigami.ContextDrawer { id: contextDrawer } pageStack.initialPage: mainPageComponent Component { id: settingsComponent Kirigami.Page { title: "Settings" objectName: "settingsPage" Rectangle { anchors.fill: parent Controls.Button { anchors.centerIn: parent text: "Remove Page" onClicked: applicationWindow().pageStack.pop(); } } } } //Main app content Component { id: mainPageComponent MainPage {} } } diff --git a/examples/simpleexamples/simpleChatApp.qml b/examples/simpleexamples/simpleChatApp.qml index 480ea0f1..a8721a14 100644 --- a/examples/simpleexamples/simpleChatApp.qml +++ b/examples/simpleexamples/simpleChatApp.qml @@ -1,337 +1,337 @@ /* * Copyright 2017 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.2 as QQC2 import org.kde.kirigami 2.4 as Kirigami Kirigami.ApplicationWindow { id: root //header: Kirigami.ToolBarApplicationHeader {} //FIXME: perhaps the default logic for going widescreen should be refined upstream wideScreen: width > columnWidth * 3 property int columnWidth: Kirigami.Units.gridUnit * 13 property int footerHeight: Math.round(Kirigami.Units.gridUnit * 2.5) globalDrawer: Kirigami.GlobalDrawer { contentItem.implicitWidth: columnWidth title: "Chat App" titleIcon: "konversation" modal: true drawerOpen: false actions: [ Kirigami.Action { text: "Rooms" iconName: "view-list-icons" }, Kirigami.Action { text: "Contacts" iconName: "tag-people" }, Kirigami.Action { text: "Search" iconName: "search" } ] } contextDrawer: Kirigami.OverlayDrawer { id: contextDrawer //they can depend on the page like that or be defined directly here - edge: Qt.RightEdge + edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge modal: !root.wideScreen onModalChanged: drawerOpen = !modal handleVisible: applicationWindow == undefined ? false : applicationWindow().controlsVisible //here padding 0 as listitems look better without as opposed to any other control topPadding: 0 bottomPadding: 0 leftPadding: 0 rightPadding: 0 contentItem: ColumnLayout { readonly property int implicitWidth: root.columnWidth spacing: 0 QQC2.Control { Layout.fillWidth: true background: Rectangle { anchors.fill: parent color: Kirigami.Theme.highlightColor opacity: 0.8 } padding: Kirigami.Units.gridUnit contentItem: ColumnLayout { id: titleLayout spacing: Kirigami.Units.gridUnit RowLayout { spacing: Kirigami.Units.gridUnit Rectangle { color: Kirigami.Theme.highlightedTextColor radius: width implicitWidth: Kirigami.Units.iconSizes.medium implicitHeight: implicitWidth } ColumnLayout { QQC2.Label { Layout.fillWidth: true color: Kirigami.Theme.highlightedTextColor text: "KDE" } QQC2.Label { Layout.fillWidth: true color: Kirigami.Theme.highlightedTextColor font.pointSize: Kirigami.Units.fontMetrics.font.pointSize * 0.8 text: "#kde: kde.org" } } } QQC2.Label { Layout.fillWidth: true color: Kirigami.Theme.highlightedTextColor text: "Main room for KDE community, other rooms are listed at kde.org/rooms" wrapMode: Text.WordWrap } } } Kirigami.Separator { Layout.fillWidth: true } QQC2.ScrollView { Layout.fillWidth: true Layout.fillHeight: true ListView { model: 50 delegate: Kirigami.BasicListItem { label: "Person " + modelData separatorVisible: false reserveSpaceForIcon: false } } } Kirigami.Separator { Layout.fillWidth: true Layout.maximumHeight: 1//implicitHeight } Kirigami.BasicListItem { label: "Group call" icon: "call-start" separatorVisible: false } Kirigami.BasicListItem { label: "Send Attachment" icon: "mail-attachment" separatorVisible: false } } } pageStack.defaultColumnWidth: columnWidth pageStack.initialPage: [channelsComponent, chatComponent] Component { id: channelsComponent Kirigami.ScrollablePage { title: "Channels" actions.main: Kirigami.Action { icon.name: "search" text: "Search" } background: Rectangle { anchors.fill: parent color: Kirigami.Theme.backgroundColor } footer: QQC2.ToolBar { height: root.footerHeight padding: Kirigami.Units.smallSpacing RowLayout { anchors.fill: parent spacing: Kirigami.Units.smallSpacing //NOTE: icon support in tool button in Qt 5.11 QQC2.ToolButton { Layout.fillHeight: true //make it square implicitWidth: height Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.smallMedium height: width source: "configure" } onClicked: root.pageStack.layers.push(secondLayerComponent); } QQC2.ComboBox { Layout.fillWidth: true Layout.fillHeight: true model: ["First", "Second", "Third"] } } } ListView { id: channelsList currentIndex: 2 model: 30 delegate: Kirigami.BasicListItem { label: "#Channel " + modelData checkable: true checked: channelsList.currentIndex == index separatorVisible: false reserveSpaceForIcon: false } } } } Component { id: chatComponent Kirigami.ScrollablePage { title: "#KDE" actions { left: Kirigami.Action { icon.name: "documentinfo" text: "Channel info" } main: Kirigami.Action { icon.name: "search" text: "Search" } } actions.contextualActions: [ Kirigami.Action { text: "Room Settings" iconName: "configure" Kirigami.Action { text: "Setting 1" } Kirigami.Action { text: "Setting 2" } }, Kirigami.Action { text: "Shared Media" iconName: "document-share" Kirigami.Action { text: "Media 1" } Kirigami.Action { text: "Media 2" } Kirigami.Action { text: "Media 3" } } ] background: Rectangle { anchors.fill: parent color: Kirigami.Theme.backgroundColor } footer: QQC2.Control { height: footerHeight padding: Kirigami.Units.smallSpacing background: Rectangle { color: Kirigami.Theme.backgroundColor Kirigami.Separator { Rectangle { anchors.fill: parent color: Kirigami.Theme.focusColor visible: chatTextInput.activeFocus } anchors { left: parent.left right: parent.right top: parent.top } } } contentItem: RowLayout { QQC2.TextField { Layout.fillWidth: true id: chatTextInput background: Item {} } //NOTE: icon support in tool button in Qt 5.11 QQC2.ToolButton { Layout.fillHeight: true //make it square implicitWidth: height Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.smallMedium height: width source: "go-next" } } } } ListView { id: channelsList verticalLayoutDirection: ListView.BottomToTop currentIndex: 2 model: 30 delegate: Item { height: Kirigami.Units.gridUnit * 4 ColumnLayout { x: Kirigami.Units.gridUnit anchors.verticalCenter: parent.verticalCenter QQC2.Label { text: modelData % 2 ? "John Doe" : "John Applebaum" } QQC2.Label { text: "Message " + modelData } } } } } } Component { id: secondLayerComponent Kirigami.Page { title: "Settings" background: Rectangle { color: Kirigami.Theme.backgroundColor } footer: QQC2.ToolBar { height: root.footerHeight QQC2.ToolButton { Layout.fillHeight: true //make it square implicitWidth: height Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.smallMedium height: width source: "configure" } onClicked: root.pageStack.layers.pop(); } } } } } diff --git a/src/controls/Page.qml b/src/controls/Page.qml index d9b39d4c..2b362237 100644 --- a/src/controls/Page.qml +++ b/src/controls/Page.qml @@ -1,332 +1,332 @@ /* * 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.1 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 as Kirigami import "private" import QtQuick.Templates 2.0 as T2 /** * Page is a container for all the app pages: everything pushed to the * ApplicationWindow stackView should be a Page instabnce (or a subclass, * such as ScrollablePage) * @see ScrollablePage * @inherit QtQuick.Templates.Page */ T2.Page { id: root /** * leftPadding: int * default contents padding at left */ leftPadding: Kirigami.Units.gridUnit /** * topPadding: int * default contents padding at top */ topPadding: Kirigami.Units.gridUnit /** * rightPadding: int * default contents padding at right */ rightPadding: Kirigami.Units.gridUnit /** * bottomPadding: int * default contents padding at bottom */ bottomPadding: actionButtons.item ? actionButtons.height : Kirigami.Units.gridUnit /** * flickable: Flickable * if the central element of the page is a Flickable * (ListView and Gridview as well) you can set it there. * normally, you wouldn't need to do that, but just use the * ScrollablePage element instead * @see ScrollablePage * Use this if your flickable has some non standard properties, such as not covering the whole Page */ property Flickable flickable /** * actions.contextualActions: list * Defines the contextual actions for the page: * an easy way to assign actions in the right sliding panel * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * [...] * } * @endcode * * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.Page { * [...] * actions.contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * @endcode */ //TODO: remove property alias contextualActions: actionsGroup.contextualActions /** * actions.main: Action * An optional single action for the action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.main: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias mainAction: actionsGroup.main /** * actions.left: Action * An optional extra action at the left of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.left: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias leftAction: actionsGroup.left /** * actions.right: Action * An optional extra action at the right of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.right: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias rightAction: actionsGroup.right /** * Actions properties are grouped. * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions { * main: Kirigami.Action {...} * left: Kirigami.Action {...} * right: Kirigami.Action {...} * contextualActions: [ * Kirigami.Action {...}, * Kirigami.Action {...} * ] * } * } * @endcode */ readonly property alias actions: actionsGroup /** * isCurrentPage: bool * * Specifies if it's the currently selected page in the window's pages row. * * @since 2.1 */ readonly property bool isCurrentPage: typeof applicationWindow === "undefined" || !globalToolBar.row ? true : (globalToolBar.row.layers.depth > 1 ? globalToolBar.row.layers.currentItem == root : globalToolBar.row.currentItem == root) PageActionPropertyGroup { id: actionsGroup } /** * emitted When the application requests a Back action * For instance a global "back" shortcut or the Android * Back button has been pressed. * The page can manage the back event by itself, * and if it set event.accepted = true, it will stop the main * application to manage the back event. */ signal backRequested(var event); //NOTE: This exists just because control instances require it contentItem: Item { onChildrenChanged: { //NOTE: make sure OverlaySheets are directly under the root //so they are over all the contents and don't have margins //search for an OverlaySheet, unfortunately have to blind test properties //as there is no way to get the classname from qml objects //TODO: OverlaySheets should be Popup instead? for (var i = children.length -1; i >= 0; --i) { var child = children[i]; if (child.toString().indexOf("OverlaySheet") === 0 || (child.sheetOpen !== undefined && child.open !== undefined && child.close !== undefined)) { child.parent = root; child.z = 9997 } } } } Component.onCompleted: { //FIXME: on material the shadow would bleed over root.clip = root.header != null; parentChanged(root.parent); } onParentChanged: { if (!parent) { return; } globalToolBar.stack = null; globalToolBar.row = null; if (root.parent.hasOwnProperty("__pageRow")) { globalToolBar.row = root.parent.__pageRow; } if (root.T2.StackView.view) { globalToolBar.stack = root.T2.StackView.view; globalToolBar.row = root.T2.StackView.view.parent; } if (globalToolBar.row) { globalToolBar.row.globalToolBar.actualStyleChanged.connect(globalToolBar.syncSource); globalToolBar.syncSource(); } } //global top toolbar if we are in a PageRow (in the row or as a layer) Loader { id: globalToolBar z: 9999 parent: root.clip ? root.parent : root - height: row ? row.globalToolBar.preferredHeight : 0 + height: row ? row.globalToolBar.height : 0 anchors { left: parent ? root.left : undefined right: parent ? root.right : undefined bottom: parent ? root.top : undefined } property Kirigami.PageRow row property T2.StackView stack active: row && (row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.ToolBar || globalToolBar.row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.Titles) function syncSource() { if (row && active) { setSource(Qt.resolvedUrl(row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.ToolBar ? "private/ToolBarPageHeader.qml" : "private/TitlesPageHeader.qml"), //TODO: find container reliably, remove assumption {"pageRow": Qt.binding(function() {return row}), "page": root, - "current": Qt.binding(function() {return stack ? true : row.currentIndex == root.parent.level})}); + "current": Qt.binding(function() {return stack || !root.parent ? true : row.currentIndex == root.parent.level})}); } } Separator { z: 999 anchors.verticalCenter: globalToolBar.verticalCenter height: globalToolBar.height * 0.6 //TODO: remove this assumption - visible: globalToolBar.row && globalToolBar.row.contentItem.contentX < root.parent.x - globalToolBar.row.globalToolBar.leftReservedSpace + visible: globalToolBar.row && root.parent && globalToolBar.row.contentItem.contentX < root.parent.x - globalToolBar.row.globalToolBar.leftReservedSpace Kirigami.Theme.textColor: globalToolBar.item ? globalToolBar.item.Kirigami.Theme.textColor : undefined } } //bottom action buttons Loader { id: actionButtons z: 9999 parent: root anchors { left: parent.left right: parent.right bottom: parent.bottom } //It should be T2.Page, Qt 5.7 doesn't like it property Item page: root height: item ? item.height : 0 active: typeof applicationWindow !== "undefined" && (!globalToolBar.row || globalToolBar.row.globalToolBar.actualStyle != Kirigami.ApplicationHeaderStyle.ToolBar) && //Legacy (typeof applicationWindow === "undefined" || (!applicationWindow().header || applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === -1) && (!applicationWindow().footer || applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") === -1)) source: Qt.resolvedUrl("./private/ActionButton.qml") } Layout.fillWidth: true } diff --git a/src/controls/PageRow.qml b/src/controls/PageRow.qml index 8a9e9f5f..a7ab1914 100644 --- a/src/controls/PageRow.qml +++ b/src/controls/PageRow.qml @@ -1,703 +1,709 @@ /* * 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.5 import QtQuick.Layouts 1.2 import QtQml.Models 2.2 import QtQuick.Templates 2.0 as T import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.4 import "private" as Private import "templates" as KT /** * PageRow implements a row-based navigation model, which can be used * with a set of interlinked information pages. Items are pushed in the * back of the row and the view scrolls until that row is visualized. * A PageRowcan show a single page or a multiple set of columns, depending * on the window width: on a phone a single column should be fullscreen, * while on a tablet or a desktop more than one column should be visible. * @inherit QtQuick.Templates.Control */ T.Control { id: root //BEGIN PROPERTIES /** * This property holds the number of items currently pushed onto the view */ readonly property int depth: popScrollAnim.running && popScrollAnim.pendingDepth > -1 ? popScrollAnim.pendingDepth : pagesLogic.count /** * The last Page in the Row */ readonly property Item lastItem: pagesLogic.count ? pagesLogic.get(pagesLogic.count - 1).page : null /** * The currently visible Item */ readonly property Item currentItem: mainView.currentItem ? mainView.currentItem.page : null /** * the index of the currently visible Item */ property alias currentIndex: mainView.currentIndex /** * The initial item when this PageRow is created */ property variant initialPage /** * The main flickable of this Row */ contentItem: mainView /** * The default width for a column * default is wide enough for 30 grid units. * Pages can override it with their Layout.fillWidth, * implicitWidth Layout.minimumWidth etc. */ property int defaultColumnWidth: Units.gridUnit * 20 /** * interactive: bool * If true it will be possible to go back/forward by dragging the * content themselves with a gesture. * Otherwise the only way to go back will be programmatically * default: true */ property alias interactive: mainView.interactive /** * wideMode: bool * If true, the PageRow is wide enough that willshow more than one column at once * @since 5.37 */ readonly property bool wideMode: root.width >= root.defaultColumnWidth*2 && pagesLogic.count >= 2 /** * separatorVisible: bool * True if the separator between pages should be visible * default: true * @since 5.38 */ property bool separatorVisible: true /** * globalToolBar: grouped property * Controls the appearance of an optional global toolbar for the whole PageRow. * It's a grouped property comprised of the following properties: * * style: (Kirigami.ApplicationHeaderStyle) can have the following values: * ** Auto: depending on application formfactor, it can behave automatically like other values, such as a Breadcrumb on mobile and ToolBar on desktop * ** Breadcrumb: it will show a breadcrumb of all the page titles in the stack, for easy navigation * ** Titles: each page will only have its own tile on top * ** TabBar: the global toolbar will look like a TabBar to select the pages * ** ToolBar: each page will have the title on top together buttons and menus to represent all of the page actions: not available on Mobile systems. * ** None: no global toolbar will be shown * * * actualStyle: this will represent the actual style of the toolbar: it can be different from style in the case style is Auto * * showNavigationButtons: if true, forward and backward navigation buttons will be shown on the left of the toolbar + * * minimumHeight: (int) minimum height of the + property int preferredHeight: 42 + property int maximumHeight: preferredHeight + property int leftReservedSpace: 0 + property int rightReservedSpace: 0 * @since 5.48 */ readonly property alias globalToolBar: globalToolBar //END PROPERTIES //BEGIN FUNCTIONS /** * Pushes a page on the stack. * The page can be defined as a component, item or string. * If an item is used then the page will get re-parented. * If a string is used then it is interpreted as a url that is used to load a page * component. * * @param page The page can also be given as an array of pages. * In this case all those pages will * be pushed onto the stack. The items in the stack can be components, items or * strings just like for single pages. * Additionally an object can be used, which specifies a page and an optional * properties property. * This can be used to push multiple pages while still giving each of * them properties. * When an array is used the transition animation will only be to the last page. * * @param properties The properties argument is optional and allows defining a * map of properties to set on the page. * @return The new created page */ function push(page, properties) { //don't push again things already there if (page.createObject === undefined && typeof page != "string" && pagesLogic.containsPage(page)) { print("The item " + page + " is already in the PageRow"); return; } if (popScrollAnim.running) { popScrollAnim.running = false; popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); } popScrollAnim.popPageCleanup(currentItem); // figure out if more than one page is being pushed var pages; if (page instanceof Array) { pages = page; page = pages.pop(); if (page.createObject === undefined && page.parent === undefined && typeof page != "string") { properties = properties || page.properties; page = page.page; } } // push any extra defined pages onto the stack if (pages) { var i; for (i = 0; i < pages.length; i++) { var tPage = pages[i]; var tProps; if (tPage.createObject === undefined && tPage.parent === undefined && typeof tPage != "string") { if (pagesLogic.containsPage(tPage)) { print("The item " + page + " is already in the PageRow"); continue; } tProps = tPage.properties; tPage = tPage.page; } var container = pagesLogic.initPage(tPage, tProps); pagesLogic.append(container); } } // initialize the page var container = pagesLogic.initPage(page, properties); pagesLogic.append(container); container.visible = container.page.visible = true; mainView.currentIndex = container.level; return container.page } /** * Pops a page off the stack. * @param page If page is specified then the stack is unwound to that page, * to unwind to the first page specify * page as null. * @return The page instance that was popped off the stack. */ function pop(page) { if (depth == 0) { return; } //if a pop was animating, stop it if (popScrollAnim.running) { popScrollAnim.running = false; popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); //if a push was animating, stop it } else { mainView.positionViewAtIndex(mainView.currentIndex, ListView.Beginning); } popScrollAnim.from = mainView.contentX if ((!page || !page.parent) && pagesLogic.count > 1) { page = pagesLogic.get(pagesLogic.count - 2).page; } popScrollAnim.to = page && page.parent ? page.parent.x : 0; popScrollAnim.pendingPage = page; popScrollAnim.pendingDepth = page && page.parent ? page.parent.level + 1 : 0; popScrollAnim.running = true; } SequentialAnimation { id: popScrollAnim property real from property real to property var pendingPage property int pendingDepth: -1 function popPageCleanup(page) { if (pagesLogic.count == 0) { return; } if (popScrollAnim.running) { popScrollAnim.running = false; } var oldPage = pagesLogic.get(pagesLogic.count-1).page; if (page !== undefined) { // an unwind target has been specified - pop until we find it while (page != oldPage && pagesLogic.count > 1) { pagesLogic.removePage(oldPage.parent.level); oldPage = pagesLogic.get(pagesLogic.count-1).page; } } else { pagesLogic.removePage(pagesLogic.count-1); } } NumberAnimation { target: mainView properties: "contentX" duration: Units.shortDuration from: popScrollAnim.from to: popScrollAnim.to } ScriptAction { script: { //snap mainView.flick(100, 0) popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); } } } /** * Replaces a page on the stack. * @param page The page can also be given as an array of pages. * In this case all those pages will * be pushed onto the stack. The items in the stack can be components, items or * strings just like for single pages. * Additionally an object can be used, which specifies a page and an optional * properties property. * This can be used to push multiple pages while still giving each of * them properties. * When an array is used the transition animation will only be to the last page. * @param properties The properties argument is optional and allows defining a * map of properties to set on the page. * @see push() for details. */ function replace(page, properties) { if (currentIndex>=1) popScrollAnim.popPageCleanup(pagesLogic.get(currentIndex-1).page); else if (currentIndex==0) popScrollAnim.popPageCleanup(); else console.warn("There's no page to replace"); return push(page, properties); } /** * Clears the page stack. * Destroy (or reparent) all the pages contained. */ function clear() { return pagesLogic.clearPages(); } /** * @return the page at idx * @param idx the depth of the page we want */ function get(idx) { return pagesLogic.get(idx).page; } /** * go back to the previous index and scroll to the left to show one more column */ function flickBack() { if (depth > 1) { currentIndex = Math.max(0, currentIndex - 1); } if (LayoutMirroring.enabled) { if (!mainView.atEnd) { mainViewScrollAnim.from = mainView.contentX mainViewScrollAnim.to = Math.min(mainView.contentWidth - mainView.width, mainView.contentX + defaultColumnWidth) mainViewScrollAnim.running = true; } } else { if (mainView.contentX - mainView.originX > 0) { mainViewScrollAnim.from = mainView.contentX mainViewScrollAnim.to = Math.max(mainView.originX, mainView.contentX - defaultColumnWidth) mainViewScrollAnim.running = true; } } } /** * layers: QtQuick.Controls.PageStack * Access to the modal layers. * Sometimes an application needs a modal page that always covers all the rows. * For instance the full screen image of an image viewer or a settings page. * @since 5.38 */ property alias layers: layersStack //END FUNCTIONS onInitialPageChanged: { clear(); if (initialPage) { push(initialPage, null) } } Keys.forwardTo: [currentItem] SequentialAnimation { id: mainViewScrollAnim property real from property real to NumberAnimation { target: mainView properties: "contentX" duration: Units.longDuration from: mainViewScrollAnim.from to: mainViewScrollAnim.to } ScriptAction { script: mainView.flick(100, 0) } } Private.GlobalToolBarStyleGroup { id: globalToolBar - leftReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.leftReservedSpace : 0 - rightReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.rightReservedSpace : 0 + readonly property int leftReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.leftReservedSpace : 0 + readonly property int rightReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.rightReservedSpace : 0 + readonly property int height: globalToolBarUI.height } QQC2.StackView { id: layersStack z: 99 anchors { fill: parent topMargin: depth > 1 && globalToolBarUI.visible ? globalToolBarUI.height: 0 } initialItem: mainView function clear () { //don't let it kill the main page row var d = root.depth; for (var i = 1; i < d; ++i) { pop(); } } popEnter: Transition { YAnimator { from: -height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } popExit: Transition { YAnimator { from: 0 to: height duration: Units.longDuration easing.type: Easing.OutCubic } } pushEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } pushExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } replaceEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } replaceExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } } Loader { id: globalToolBarUI anchors { left: parent.left top: parent.top right: parent.right } z: 100 active: globalToolBar.actualStyle != ApplicationHeaderStyle.None visible: active source: Qt.resolvedUrl("private/PageRowGlobalToolBarUI.qml"); } ListView { id: mainView boundsBehavior: Flickable.StopAtBounds orientation: Qt.Horizontal snapMode: ListView.SnapToItem currentIndex: root.currentIndex property int marginForLast: count > 1 ? pagesLogic.get(count-1).page.width - pagesLogic.get(count-1).width : 0 leftMargin: LayoutMirroring.enabled ? marginForLast : 0 rightMargin: LayoutMirroring.enabled ? 0 : marginForLast preferredHighlightBegin: 0 preferredHighlightEnd: 0 highlightMoveDuration: Units.longDuration highlightFollowsCurrentItem: true onMovementEnded: currentIndex = Math.max(0, indexAt(contentX, 0)) onFlickEnded: onMovementEnded(); onCurrentIndexChanged: { if (currentItem) { currentItem.page.forceActiveFocus(); } } model: ObjectModel { id: pagesLogic readonly property var componentCache: new Array() readonly property int roundedDefaultColumnWidth: root.width < root.defaultColumnWidth*2 ? root.width : root.defaultColumnWidth function removePage(id) { if (id < 0 || id >= count) { print("Tried to remove an invalid page index:" + id); return; } var item = pagesLogic.get(id); if (item.owner) { item.page.visible = false; item.page.parent = item.owner; } //FIXME: why reparent ing is necessary? //is destroy just an async deleteLater() that isn't executed immediately or it actually leaks? pagesLogic.remove(id); item.parent = root; if (item.page.parent==item) { item.page.destroy(1) } item.destroy(); } function clearPages () { popScrollAnim.running = false; popScrollAnim.pendingDepth = -1; while (count > 0) { removePage(count-1); } } function initPage(page, properties) { var container = containerComponent.createObject(mainView, { "level": pagesLogic.count, "page": page }); var pageComp; if (page.createObject) { // page defined as component pageComp = page; } else if (typeof page == "string") { // page defined as string (a url) pageComp = pagesLogic.componentCache[page]; if (!pageComp) { pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page); } } if (pageComp) { if (pageComp.status == Component.Error) { throw new Error("Error while loading page: " + pageComp.errorString()); } else { // instantiate page from component page = pageComp.createObject(container.pageParent, properties || {}); } } else { // copy properties to the page for (var prop in properties) { if (properties.hasOwnProperty(prop)) { page[prop] = properties[prop]; } } } container.page = page; if (page.parent == null || page.parent == container.pageParent) { container.owner = null; } // the page has to be reparented if (page.parent != container) { page.parent = container; } return container; } function containsPage(page) { for (var i = 0; i < pagesLogic.count; ++i) { var candidate = pagesLogic.get(i); if (candidate.page == page) { print("The item " + page + " is already in the PageRow"); return; } } } } T.ScrollIndicator.horizontal: T.ScrollIndicator { anchors { left: parent.left right: parent.right bottom: parent.bottom } height: Units.smallSpacing contentItem: Rectangle { height: Units.smallSpacing width: Units.smallSpacing color: Theme.textColor opacity: 0 onXChanged: { opacity = 0.3 scrollIndicatorTimer.restart(); } Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } Timer { id: scrollIndicatorTimer interval: Units.longDuration * 4 onTriggered: parent.opacity = 0; } } } onContentWidthChanged: mainView.positionViewAtIndex(root.currentIndex, ListView.Contain) } Component { id: containerComponent MouseArea { id: container height: mainView.height width: root.width state: page ? (!root.wideMode ? "vertical" : (container.level >= pagesLogic.count - 1 ? "last" : "middle")) : ""; acceptedButtons: Qt.LeftButton | Qt.BackButton | Qt.ForwardButton property int level readonly property int hint: page && page.implicitWidth ? page.implicitWidth : root.defaultColumnWidth readonly property int roundedHint: Math.floor(root.width/hint) > 0 ? root.width/Math.floor(root.width/hint) : root.width property T.Control __pageRow: root property Item footer property Item page onPageChanged: { if (page) { owner = page.parent; page.parent = container; page.anchors.left = container.left; page.anchors.top = container.top; page.anchors.right = container.right; page.anchors.bottom = container.bottom; - page.anchors.topMargin = Qt.binding(function() {return globalToolBar.preferredHeight}); + page.anchors.topMargin = Qt.binding(function() {return globalToolBarUI.height}); } } property Item owner drag.filterChildren: true onClicked: { switch (mouse.button) { case Qt.BackButton: root.flickBack(); break; case Qt.ForwardButton: root.currentIndex = Math.min(root.depth, root.currentIndex + 1); break; default: root.currentIndex = level; break; } } onFocusChanged: { if (focus) { root.currentIndex = level; } } Separator { z: 999 anchors { top: page ? page.top : parent.top bottom: parent.bottom left: parent.left //ensure a sharp angle topMargin: -width } visible: root.separatorVisible && mainView.contentX < container.x } states: [ State { name: "vertical" PropertyChanges { target: container width: root.width } PropertyChanges { target: container.page ? container.page.anchors : null rightMargin: 0 } }, State { name: "last" PropertyChanges { target: container width: pagesLogic.roundedDefaultColumnWidth } PropertyChanges { target: container.page.anchors rightMargin: { return -(root.width - pagesLogic.roundedDefaultColumnWidth*2); } } }, State { name: "middle" PropertyChanges { target: container width: pagesLogic.roundedDefaultColumnWidth } PropertyChanges { target: container.page.anchors rightMargin: 0 } } ] } } } diff --git a/src/controls/private/AbstractPageHeader.qml b/src/controls/private/AbstractPageHeader.qml index b71157f0..c96f524f 100644 --- a/src/controls/private/AbstractPageHeader.qml +++ b/src/controls/private/AbstractPageHeader.qml @@ -1,37 +1,38 @@ /* * 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.5 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 AbstractApplicationHeader { anchors.fill: parent property Item container property bool current - minimumHeight: parent.height - maximumHeight: parent.height - preferredHeight: parent.height + minimumHeight: pageRow.globalToolBar.minimumHeight + maximumHeight: pageRow.globalToolBar.maximumHeight + preferredHeight: pageRow.globalToolBar.preferredHeight + implicitHeight: page.y - leftPadding: pageRow ? Math.max(0, pageRow.contentItem.contentX - mapToItem(pageRow.contentItem.contentItem, 0, 0).x + pageRow.globalToolBar.leftReservedSpace) : 0 + leftPadding: Math.max(0, pageRow.contentItem.contentX - mapToItem(pageRow.contentItem.contentItem, 0, 0).x + pageRow.globalToolBar.leftReservedSpace) - rightPadding: pageRow ? Math.min(pageRow.globalToolBar.rightReservedSpace, Math.max(0, mapToItem(pageRow.contentItem.contentItem, width, 0).x - (pageRow.contentItem.contentX + pageRow.width) + pageRow.globalToolBar.rightReservedSpace)) : 0 + rightPadding: Math.min(pageRow.globalToolBar.rightReservedSpace, Math.max(0, mapToItem(pageRow.contentItem.contentItem, width, 0).x - (pageRow.contentItem.contentX + pageRow.width) + pageRow.globalToolBar.rightReservedSpace)) } diff --git a/src/controls/private/GlobalToolBarStyleGroup.qml b/src/controls/private/GlobalToolBarStyleGroup.qml index bb4e66c6..9131b186 100644 --- a/src/controls/private/GlobalToolBarStyleGroup.qml +++ b/src/controls/private/GlobalToolBarStyleGroup.qml @@ -1,50 +1,48 @@ /* * 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.1 import org.kde.kirigami 2.4 as Kirigami QtObject { id: globalToolBar property int style: Kirigami.ApplicationHeaderStyle.None readonly property int actualStyle: { if (style == Kirigami.ApplicationHeaderStyle.Auto) { //Legacy: if ApplicationHeader or ToolbarApplicationHeader are in the header or footer, disable the toolbar here if (typeof applicationWindow !== "undefined" && applicationWindow().header && applicationWindow().header.toString().indexOf("ApplicationHeader") !== -1) { return Kirigami.ApplicationHeaderStyle.None } //non legacy logic return (Kirigami.Settings.isMobile ? (root.wideMode ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.Breadcrumb) : Kirigami.ApplicationHeaderStyle.ToolBar) } else { //forbid ToolBar on mobile systems return Kirigami.Settings.isMobile && style == Kirigami.ApplicationHeaderStyle.ToolBar ? Kirigami.ApplicationHeaderStyle.Breadcrumb : style; } } property bool showNavigationButtons: style != Kirigami.ApplicationHeaderStyle.TabBar && (!Kirigami.Settings.isMobile || Qt.platform.os == "ios") property int minimumHeight: 0 property int preferredHeight: 42 property int maximumHeight: preferredHeight - property int leftReservedSpace: 0 - property int rightReservedSpace: 0 } diff --git a/src/controls/private/PageRowGlobalToolBarUI.qml b/src/controls/private/PageRowGlobalToolBarUI.qml index e15e6add..ec2ba66a 100644 --- a/src/controls/private/PageRowGlobalToolBarUI.qml +++ b/src/controls/private/PageRowGlobalToolBarUI.qml @@ -1,85 +1,85 @@ /* * 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.1 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 as Kirigami import "../templates/private" as TemplatesPrivate Kirigami.AbstractApplicationHeader { id: header readonly property int leftReservedSpace: buttonsLayout.visible && buttonsLayout.visibleChildren.length > 1 ? buttonsLayout.width : 0 - readonly property int rightReservedSpace: typeof applicationWindow() !== "undefined" && applicationWindow().globalDrawer.handle.handleAnchor == root ? backButton.background.implicitHeight : 0 + readonly property int rightReservedSpace: typeof applicationWindow() !== "undefined" && applicationWindow().contextDrawer && applicationWindow().contextDrawer.handle.handleAnchor == root ? backButton.background.implicitHeight : 0 height: visible ? implicitHeight : 0 minimumHeight: globalToolBar.minimumHeight preferredHeight: globalToolBar.preferredHeight maximumHeight: globalToolBar.maximumHeight RowLayout { anchors.fill: parent spacing: 0 RowLayout { id: buttonsLayout visible: globalToolBar.showNavigationButtons && globalToolBar.actualStyle != Kirigami.ApplicationHeaderStyle.None Item { visible: typeof applicationWindow() !== "undefined" && applicationWindow().globalDrawer.handle.handleAnchor == root Layout.preferredWidth: backButton.background.implicitHeight Layout.preferredHeight: backButton.background.implicitHeight } TemplatesPrivate.BackButton { id: backButton Layout.preferredWidth: background.implicitHeight Layout.preferredHeight: background.implicitHeight } TemplatesPrivate.ForwardButton { Layout.preferredWidth: background.implicitHeight Layout.preferredHeight: background.implicitHeight } Kirigami.Separator { Layout.preferredHeight: parent.parent.height * 0.6 //FIXME: hacky opacity: buttonsLayout.visibleChildren.length > 1 } } Loader { id: breadcrumbLoader Layout.fillWidth: true Layout.fillHeight: true active: globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.TabBar || globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.Breadcrumb //TODO: different implementation? sourceComponent: Kirigami.ApplicationHeader { minimumHeight: height preferredHeight: height maximumHeight: height backButtonEnabled: false anchors.fill:parent background.visible: false headerStyle: globalToolBar.style } } } background.visible: breadcrumbLoader.active } diff --git a/src/controls/private/TitlesPageHeader.qml b/src/controls/private/TitlesPageHeader.qml index 6738f128..f2883151 100644 --- a/src/controls/private/TitlesPageHeader.qml +++ b/src/controls/private/TitlesPageHeader.qml @@ -1,40 +1,43 @@ /* * 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.5 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 AbstractPageHeader { id: root Heading { id: title + anchors.fill: parent leftPadding: Units.largeSpacing opacity: root.current ? 1 : 0.4 maximumLineCount: 1 color: Theme.textColor elide: Text.ElideRight + font.pointSize: -1 + font.pixelSize: Math.max(1, height*0.7) text: page ? page.title : "" } } diff --git a/src/controls/templates/AbstractApplicationHeader.qml b/src/controls/templates/AbstractApplicationHeader.qml index f67765b4..08b29071 100644 --- a/src/controls/templates/AbstractApplicationHeader.qml +++ b/src/controls/templates/AbstractApplicationHeader.qml @@ -1,192 +1,182 @@ /* * 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.5 import QtQuick.Layouts 1.2 import "private" import org.kde.kirigami 2.4 /** * An item that can be used as a title for the application. * Scrolling the main page will make it taller or shorter (trough the point of going away) * It's a behavior similar to the typical mobile web browser adressbar * the minimum, preferred and maximum heights of the item can be controlled with * * minimumHeight: default is 0, i.e. hidden * * preferredHeight: default is Units.gridUnit * 1.6 * * preferredHeight: default is Units.gridUnit * 3 * * To achieve a titlebar that stays completely fixed just set the 3 sizes as the same * @inherit QtQuick.Item */ Item { id: root z: 90 property int minimumHeight: 0 property int preferredHeight: Units.gridUnit * 2 property int maximumHeight: Units.gridUnit * 3 property PageRow pageRow: __appWindow.pageStack property Page page: pageRow.currentItem default property alias contentItem: mainItem.data readonly property int paintedHeight: headerItem.y + headerItem.height - 1 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true property int leftPadding: 0 property int topPadding: 0 property int rightPadding: 0 property int bottomPadding: 0 //FIXME: remove property QtObject __appWindow: applicationWindow(); anchors { left: parent.left right: parent.right } implicitHeight: preferredHeight /** * background: Item * This property holds the background item. * Note: the background will be automatically sized as the whole control */ property Item background - onContentItemChanged: { - if (contentItem.length > 0) { - var item = contentItem[contentItem.length - 1]; - item.parent = mainItem; - item.anchors.top = mainItem.top; - item.anchors.right = mainItem.right; - item.anchors.bottom = mainItem.bottom; - item.width = Qt.binding(function() {return Math.max(mainItem.width, item.Layout.minimumWidth)}); - } - } onBackgroundChanged: { background.z = -1; background.parent = headerItem; background.anchors.fill = headerItem; } opacity: height > 0 ? 1 : 0 Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } Behavior on height { enabled: root.page && root.page.flickable && !root.page.flickable.moving NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Connections { target: __appWindow onControlsVisibleChanged: root.implicitHeight = __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 + height: __appWindow.reachableMode && __appWindow.reachableModeEnabled ? root.maximumHeight : root.height Connections { id: headerSlideConnection target: root.page ? root.page.flickable : null property int oldContentY onContentYChanged: { if (!Settings.isMobile || !__appWindow.controlsVisible || !root.page || root.page.flickable.atYBeginning || root.page.flickable.atYEnd) { return; //if moves but not dragging, just update oldContentY } else if (!root.page.flickable.dragging) { oldContentY = root.page.flickable.contentY; return; } if (__appWindow.wideScreen || !Settings.isMobile) { root.implicitHeight = root.preferredHeight; } else { var oldHeight = root.height; root.implicitHeight = Math.max(root.minimumHeight, Math.min(root.preferredHeight, root.height + oldContentY - root.page.flickable.contentY)); //if the height is changed, use that to simulate scroll if (oldHeight != height) { root.page.flickable.contentY = oldContentY; } else { oldContentY = root.page.flickable.contentY; } } } onMovementEnded: { if (__appWindow.wideScreen || !Settings.isMobile) { return; } if (root.height > (root.preferredHeight - root.minimumHeight)/2 ) { root.implicitHeight = root.preferredHeight; } else { root.implicitHeight = root.minimumHeight; } } } Connections { target: pageRow onCurrentItemChanged: { if (!root.page) { return; } if (root.page.flickable) { headerSlideConnection.oldContentY = root.page.flickable.contentY; } else { headerSlideConnection.oldContentY = 0; } root.implicitHeight = root.preferredHeight; } } Item { id: mainItem clip: childrenRect.width > width anchors { fill: parent leftMargin: root.leftPadding topMargin: root.topPadding rightMargin: root.rightPadding bottomMargin: root.bottomPadding } } } } diff --git a/src/controls/templates/ApplicationHeader.qml b/src/controls/templates/ApplicationHeader.qml index 4093c088..5dd806b6 100644 --- a/src/controls/templates/ApplicationHeader.qml +++ b/src/controls/templates/ApplicationHeader.qml @@ -1,389 +1,390 @@ /* * 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.5 import QtQuick.Controls 2.0 as QQC2 import QtQuick.Layouts 1.2 import "private" import org.kde.kirigami 2.4 /** * An item that can be used as a title for the application. * Scrolling the main page will make it taller or shorter (trough the point of going away) * It's a behavior similar to the typical mobile web browser adressbar * the minimum, preferred and maximum heights of the item can be controlled with * * minimumHeight: default is 0, i.e. hidden * * preferredHeight: default is Units.gridUnit * 1.6 * * maximumHeight: default is Units.gridUnit * 3 * * To achieve a titlebar that stays completely fixed just set the 3 sizes as the same */ AbstractApplicationHeader { id: header /** * headerStyle: int * The way the separator between pages should be drawn in the header. * Allowed values are: * * Breadcrumb: the pages are hyerarchical and the separator will look like a > * * TabBar: the pages are intended to behave like tabbar pages * and the separator will look limke a dot. * * When the heaer is in wide screen mode, no separator will be drawn. */ property int headerStyle: ApplicationHeaderStyle.Auto /** * backButtonEnabled: bool * if true, there will be a back button present that will make the pagerow scroll back when clicked */ property bool backButtonEnabled: (!titleList.isTabBar && (!Settings.isMobile || Qt.platform.os == "ios")) onBackButtonEnabledChanged: { if (backButtonEnabled && !titleList.backButton) { var component = Qt.createComponent(Qt.resolvedUrl("private/BackButton.qml")); titleList.backButton = component.createObject(navButtons); component = Qt.createComponent(Qt.resolvedUrl("private/ForwardButton.qml")); titleList.forwardButton = component.createObject(navButtons, {"headerFlickable": titleList}); } else if (titleList.backButton) { titleList.backButton.destroy(); titleList.forwardButton.destroy(); } } property Component pageDelegate: Component { Row { height: parent.height spacing: Units.smallSpacing x: Units.smallSpacing Icon { //in tabbar mode this is just a spacer visible: !titleList.wideMode && ((typeof(modelData) != "undefined" && modelData > 0) || titleList.internalHeaderStyle == ApplicationHeaderStyle.TabBar) anchors.verticalCenter: parent.verticalCenter height: Units.iconSizes.small width: height selected: header.background && header.background.color && header.background.color == Theme.highlightColor source: titleList.isTabBar ? "" : (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic") } Heading { id: title width: Math.min(parent.width, Math.min(titleList.width, implicitWidth)) + Units.smallSpacing anchors.verticalCenter: parent.verticalCenter opacity: current ? 1 : 0.4 //Scaling animate NativeRendering is too slow renderType: Text.QtRendering color: header.background && header.background.color && header.background.color == Theme.highlightColor ? Theme.highlightedTextColor : Theme.textColor elide: Text.ElideRight text: page ? page.title : "" - font.pointSize: Math.max(1, titleList.height / (1.6 * Units.devicePixelRatio)) + font.pointSize: -1 + font.pixelSize: Math.max(1, titleList.height * 0.7) verticalAlignment: Text.AlignVCenter wrapMode: Text.NoWrap Rectangle { anchors { bottom: parent.bottom left: parent.left right: parent.right } height: Units.smallSpacing color: title.color opacity: 0.6 visible: titleList.isTabBar && current } } } } Rectangle { anchors { right: titleList.left verticalCenter: parent.verticalCenter } visible: titleList.x > 0 && !titleList.atXBeginning height: parent.height * 0.7 color: Theme.highlightedTextColor width: Math.ceil(Units.smallSpacing / 6) opacity: 0.4 } QQC2.StackView { id: stack anchors { fill: parent leftMargin: navButtons.width rightMargin: __appWindow.contextDrawer && __appWindow.contextDrawer.handleVisible && __appWindow.contextDrawer.handle && __appWindow.contextDrawer.handle.y == 0 ? __appWindow.contextDrawer.handle.width : 0 } initialItem: titleList popEnter: Transition { YAnimator { from: -height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } popExit: Transition { YAnimator { from: 0 to: height duration: Units.longDuration easing.type: Easing.OutCubic } } pushEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } pushExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } replaceEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } replaceExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } } Separator { id: separator height: parent.height * 0.6 visible: navButtons.width > 0 anchors { verticalCenter: parent.verticalCenter left: navButtons.right } } Separator { height: parent.height * 0.6 visible: stack.anchors.rightMargin > 0 anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: stack.anchors.rightMargin } } Repeater { model: pageRow.layers.depth -1 delegate: Loader { sourceComponent: header.pageDelegate readonly property Page page: pageRow.layers.get(modelData+1) readonly property bool current: true; Component.onCompleted: stack.push(this) Component.onDestruction: stack.pop() } } Row { id: navButtons anchors { left: parent.left top: parent.top bottom: parent.bottom topMargin: Units.smallSpacing bottomMargin: Units.smallSpacing } Item { height: parent.height width: __appWindow.globalDrawer && __appWindow.globalDrawer.handleVisible && __appWindow.globalDrawer.handle && __appWindow.globalDrawer.handle.y == 0 ? __appWindow.globalDrawer.handle.width : 0 } } Flickable { id: titleList readonly property bool wideMode: header.headerStyle != ApplicationHeaderStyle.Breadcrumb && typeof pageRow.wideMode !== "undefined" ? pageRow.wideMode : __appWindow.wideMode property int internalHeaderStyle: header.headerStyle == ApplicationHeaderStyle.Auto ? (titleList.wideMode ? ApplicationHeaderStyle.Titles : ApplicationHeaderStyle.Breadcrumb) : header.headerStyle //if scrolling the titlebar should scroll also the pages and vice versa property bool scrollingLocked: (header.headerStyle == ApplicationHeaderStyle.Titles || titleList.wideMode) //uses this to have less strings comparisons property bool scrollMutex property bool isTabBar: header.headerStyle == ApplicationHeaderStyle.TabBar property Item backButton property Item forwardButton clip: true boundsBehavior: Flickable.StopAtBounds readonly property alias model: mainRepeater.model contentWidth: contentItem.width contentHeight: height readonly property int currentIndex: pageRow && pageRow.currentIndex !== undefined ? pageRow.currentIndex : 0 readonly property int count: mainRepeater.count function gotoIndex(idx) { //don't actually scroll in widescreen mode if (titleList.wideMode || contentItem.children.length < 2) { return; } listScrollAnim.running = false var pos = titleList.contentX; var destPos; titleList.contentX = Math.max((contentItem.children[idx].x + contentItem.children[idx].width) - titleList.width, Math.min(titleList.contentX, contentItem.children[idx].x)); destPos = titleList.contentX; listScrollAnim.from = pos; listScrollAnim.to = destPos; listScrollAnim.running = true; } NumberAnimation { id: listScrollAnim target: titleList property: "contentX" duration: Units.longDuration easing.type: Easing.InOutQuad } Timer { id: contentXSyncTimer interval: 0 onTriggered: { titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX; } } onCountChanged: contentXSyncTimer.restart(); onCurrentIndexChanged: gotoIndex(currentIndex); onModelChanged: gotoIndex(currentIndex); onContentWidthChanged: gotoIndex(currentIndex); onContentXChanged: { if (movingHorizontally && !titleList.scrollMutex && titleList.scrollingLocked && !pageRow.contentItem.moving) { titleList.scrollMutex = true; pageRow.contentItem.contentX = titleList.contentX - titleList.originX + pageRow.contentItem.originX; titleList.scrollMutex = false; } } onHeightChanged: { titleList.returnToBounds() } onMovementEnded: { if (titleList.scrollingLocked) { //this will trigger snap as well pageRow.contentItem.flick(0,0); } } onFlickEnded: movementEnded(); NumberAnimation { id: scrollTopAnimation target: pageRow.currentItem && pageRow.currentItem.flickable ? pageRow.currentItem.flickable : null property: "contentY" to: 0 duration: Units.longDuration easing.type: Easing.InOutQuad } Row { id: contentItem spacing: 0 Repeater { id: mainRepeater model: pageRow.depth delegate: MouseArea { id: delegate readonly property int currentIndex: index readonly property var currentModelData: modelData clip: true width: { //more columns shown? if (titleList.scrollingLocked && delegateLoader.page) { return delegateLoader.page.width - (index == 0 ? navButtons.width : 0) - (index == pageRow.depth-1 ? stack.anchors.rightMargin : 0); } else { return Math.min(titleList.width, delegateLoader.implicitWidth + Units.smallSpacing); } } height: titleList.height onClicked: { if (pageRow.currentIndex == modelData) { //scroll up if current otherwise make current if (!pageRow.currentItem.flickable) { return; } if (pageRow.currentItem.flickable.contentY > -__appWindow.header.height) { scrollTopAnimation.to = -pageRow.currentItem.flickable.topMargin; scrollTopAnimation.running = true; } } else { pageRow.currentIndex = modelData; } } Loader { id: delegateLoader height: parent.height x: titleList.wideMode || headerStyle == ApplicationHeaderStyle.Titles ? (Math.min(delegate.width - implicitWidth, Math.max(0, titleList.contentX - delegate.x))) : 0 width: parent.width - x Connections { target: delegateLoader.page Component.onDestruction: delegateLoader.sourceComponent = null } sourceComponent: header.pageDelegate readonly property Page page: pageRow.get(modelData) //NOTE: why not use ListViewCurrentIndex? because listview itself resets //currentIndex in some situations (since here we are using an int as a model, //even more often) so the property binding gets broken readonly property bool current: pageRow.currentIndex == index readonly property int index: parent.currentIndex readonly property var modelData: parent.currentModelData } } } } Connections { target: titleList.scrollingLocked ? pageRow.contentItem : null onContentXChanged: { if (!titleList.dragging && !titleList.movingHorizontally && !titleList.scrollMutex) { titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX; } } } } }