diff --git a/examples/gallerydata/contents/ui/DesktopExampleApp.qml b/examples/gallerydata/contents/ui/DesktopExampleApp.qml index 56aedcea..61f65b37 100644 --- a/examples/gallerydata/contents/ui/DesktopExampleApp.qml +++ b/examples/gallerydata/contents/ui/DesktopExampleApp.qml @@ -1,136 +1,139 @@ /* * 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.2 as Kirigami import "gallery" Kirigami.ApplicationWindow { id: root header: Kirigami.ToolBarApplicationHeader {} globalDrawer: Kirigami.GlobalDrawer { title: "Widget gallery" titleIcon: "applications-graphics" bannerImageSource: "banner.jpg" actions: [ Kirigami.Action { text: "Submenu 1" iconName: "view-list-icons" Kirigami.Action { text: "Action 1" onTriggered: showPassiveNotification(text + " clicked") } Kirigami.Action { text: "Action 2" onTriggered: showPassiveNotification(text + " clicked") } Kirigami.Action { text: "Action 3" onTriggered: showPassiveNotification(text + " clicked") } }, 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" + icon { + name: "configure" + color: Kirigami.Theme.activeTextColor + } 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 } } 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/gallerydata/contents/ui/gallery/MiscGallery.qml b/examples/gallerydata/contents/ui/gallery/MiscGallery.qml index af4ffd9d..f3a34c45 100644 --- a/examples/gallerydata/contents/ui/gallery/MiscGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/MiscGallery.qml @@ -1,191 +1,203 @@ /* * Copyright 2016 Aleix Pol Gonzalez * * 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.1 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 ScrollablePage { id: page title: "Misc controls" actions { main: Action { - iconName: "document-edit" + icon { + name: "document-edit" + color: Theme.negativeTextColor + } text: "Main Action Text" } left: Action { - iconName: "go-previous" + icon { + name: "go-previous" + color: Theme.positiveTextColor + } text: "Left Action Text" onTriggered: { showPassiveNotification("Left action triggered") } } right: Action { - iconName: "go-next" + icon { + name: "go-next" + color: Theme.neutralTextColor + } text: "Right Action Text" onTriggered: { showPassiveNotification("Right action triggered") } } contextualActions: [ Action { text:"Action for buttons" - iconName: "bookmarks" + icon { + name: "bookmarks" + color: Theme.activeTextColor + } onTriggered: showPassiveNotification("Action 1 clicked") }, Action { text:"Disabled Action" - iconName: "folder" + icon.name: "folder" enabled: false }, Action { text: "Action for Sheet" visible: sheet.sheetOpen } ] } header: Controls.ToolBar { RowLayout { anchors.verticalCenter: parent.verticalCenter Controls.ToolButton { text: "ToolButton" } Controls.ToolButton { text: "Menu" onClicked: menu.open(); Controls.Menu { id: menu y: parent.height Controls.MenuItem { checkable: true text: "Item1" } Controls.MenuItem { text: "Item2" } } } } } footer: Rectangle { color: Theme.backgroundColor height: Units.gridUnit * 3 Controls.TextField { topPadding: 0 bottomPadding: 0 leftPadding: Units.smallSpacing rightPadding: Units.smallSpacing anchors.fill: parent } Separator { anchors { top: parent.top left: parent.left right: parent.right } } } Controls.Dialog { id: dialog modal: true focus: true x: (page.width - width) / 2 y: page.height / 2 - height width: Math.min(page.width - Units.gridUnit * 4, Units.gridUnit * 20) standardButtons: Controls.Dialog.Ok title: "Title" Controls.Label { width: dialog.availableWidth text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan. Nunc vestibulum placerat bibendum. Morbi commodo auctor varius. Donec molestie euismod ultrices. Sed facilisis augue nec eros auctor." wrapMode: Label.Wrap } } ColumnLayout { anchors.centerIn: parent Controls.Button { Layout.alignment: Qt.AlignHCenter text: "Dialog" onClicked: dialog.open() } Controls.Dial { Layout.alignment: Qt.AlignHCenter } Controls.SpinBox { editable: true Layout.alignment: Qt.AlignHCenter } Controls.ComboBox { model: ["First", "Second", "Third"] Layout.alignment: Qt.AlignHCenter } Controls.GroupBox { title: "Title" Layout.alignment: Qt.AlignHCenter ColumnLayout { id: options Controls.RadioButton { text: "First" checked: true } Controls.RadioButton { text: "Second" checked: false } Controls.RadioButton { text: "Third" checked: false } } } Column { Layout.alignment: Qt.AlignHCenter Controls.ItemDelegate { width: 300 text: "Delegate1" } Controls.ItemDelegate { width: 300 text: "Delegate2" } Controls.CheckDelegate { width: 300 text: "Delegate3" } Controls.SwitchDelegate { width: 300 text: "Delegate4" } Controls.RadioDelegate { width: 300 text: "Delegate5" } } } } diff --git a/examples/gallerydata/contents/ui/gallery/NonScrollableGallery.qml b/examples/gallerydata/contents/ui/gallery/NonScrollableGallery.qml index 12a40f77..9e608f69 100644 --- a/examples/gallerydata/contents/ui/gallery/NonScrollableGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/NonScrollableGallery.qml @@ -1,57 +1,59 @@ /* * 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.0 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.2 Page { id: page Layout.fillWidth: true title: "Simple Page" actions { main: Action { - iconName: sheet.sheetOpen ? "dialog-cancel" : "document-edit" + icon { + name: sheet.sheetOpen ? "dialog-cancel" : "document-edit" + } text: "Main Action Text" checkable: true onCheckedChanged: sheet.sheetOpen = checked; } } Rectangle { anchors.fill: parent color: "red" Controls.Label { anchors.centerIn: parent text: "Rectangle with automatic margins" } } OverlaySheet { id: sheet onSheetOpenChanged: page.actions.main.checked = sheetOpen; Controls.Label { property int implicitWidth: Units.gridUnit * 30 wrapMode: Text.WordWrap text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan. Nunc vestibulum placerat bibendum. Morbi commodo auctor varius. Donec molestie euismod ultrices. Sed facilisis augue nec eros auctor, vitae mattis quam rhoncus. Nam ut erat diam. Curabitur iaculis accumsan magna, eget fermentum massa scelerisque eu. Cras elementum erat non erat euismod accumsan. Vestibulum ac mi sed dui finibus pulvinar. Vivamus dictum, leo sed lobortis porttitor, nisl magna faucibus orci, sit amet euismod arcu elit eget est. Duis et vehicula nibh. In arcu sapien, laoreet sit amet porttitor non, rhoncus vel magna. Suspendisse imperdiet consectetur est nec ornare. Pellentesque bibendum sapien at erat efficitur vehicula. Morbi sed porta nibh. Vestibulum ut urna ut dolor sagittis mattis." } } } diff --git a/examples/gallerydata/contents/ui/gallery/SliderGallery.qml b/examples/gallerydata/contents/ui/gallery/SliderGallery.qml index 24b304b5..119a7685 100644 --- a/examples/gallerydata/contents/ui/gallery/SliderGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/SliderGallery.qml @@ -1,122 +1,122 @@ /* * 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.0 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 ScrollablePage { id: page Layout.fillWidth: true title: "Sliders" actions { left: Action { iconName: "folder-sync" text: "Left Action Text" onTriggered: { showPassiveNotification("Left action triggered") } } right: Action { iconName: "configure" text: "Right Action Text" onTriggered: { showPassiveNotification("Right action triggered") } } } ColumnLayout { width: page.width Item { Layout.fillWidth: true Layout.minimumHeight: Units.gridUnit * 20 ColumnLayout { anchors.centerIn: parent spacing: Units.smallSpacing - Label { + Controls.Label { text: "Normal:" } Controls.Slider { id: slider Layout.minimumWidth: Units.gridUnit * 15 value: 2 to: 5.0 Controls.ToolTip { parent: slider.handle visible: slider.pressed text: slider.position.toFixed(1) } } - Label { + Controls.Label { text: "Disabled:" } Controls.Slider { enabled: false Layout.minimumWidth: Units.gridUnit * 15 value: 2 to: 5.0 } - Label { + Controls.Label { text: "Thickmarks:" } Controls.Slider { id: slider2 Layout.minimumWidth: Units.gridUnit * 15 to: 5.0 stepSize: 1.0 value: 3 Controls.ToolTip { parent: slider2.handle visible: slider2.pressed text: slider2.position.toFixed(1) } } Controls.RangeSlider {} - Label { + Controls.Label { text: "Vertical:" } RowLayout { Layout.alignment: Qt.AlignHCenter Controls.Slider { Layout.minimumWidth: 2 Layout.minimumHeight: Units.gridUnit * 10 value: 2 to: 5.0 orientation: Qt.Vertical } Controls.Slider { Layout.minimumWidth: 2 Layout.minimumHeight: Units.gridUnit * 10 value: 3 to: 5.0 stepSize: 1.0 orientation: Qt.Vertical } } } } } } diff --git a/examples/simpleexamples/MultipleColumnsGallery.qml b/examples/simpleexamples/MultipleColumnsGallery.qml index 483fb18d..376336e8 100644 --- a/examples/simpleexamples/MultipleColumnsGallery.qml +++ b/examples/simpleexamples/MultipleColumnsGallery.qml @@ -1,88 +1,88 @@ /* * Copyright 2015 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.0 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 ScrollablePage { id: page Layout.fillWidth: true implicitWidth: Units.gridUnit * (Math.floor(Math.random() * 35) + 8) title: "Multiple Columns" actions { contextualActions: [ Action { text:"Action for buttons" iconName: "bookmarks" onTriggered: print("Action 1 clicked") }, Action { text:"Action 2" iconName: "folder" enabled: false } ] } ColumnLayout { width: page.width spacing: Units.smallSpacing - Label { + Controls.Label { Layout.fillWidth: true wrapMode: Text.WordWrap text: "This page is used to test multiple columns: you can push and pop an arbitrary number of pages, each new page will have a random implicit width between 8 and 35 grid units.\nIf you enlarge the window enough, you can test how the application behaves with multiple columns." } Item { Layout.minimumWidth: Units.gridUnit *2 Layout.minimumHeight: Layout.minimumWidth } - Label { + Controls.Label { anchors.horizontalCenter: parent.horizontalCenter text: "Page implicitWidth: " + page.implicitWidth } Controls.Button { text: "Push Another Page" anchors.horizontalCenter: parent.horizontalCenter onClicked: pageStack.push(Qt.resolvedUrl("MultipleColumnsGallery.qml")); } Controls.Button { text: "Pop A Page" anchors.horizontalCenter: parent.horizontalCenter onClicked: pageStack.pop(); } RowLayout { anchors.horizontalCenter: parent.horizontalCenter Controls.TextField { id: edit text: page.title } Controls.Button { text: "Rename Page" onClicked: page.title = edit.text; } } } } diff --git a/src/controls/Action.qml b/src/controls/Action.qml index 71c0b58a..96abb6bb 100644 --- a/src/controls/Action.qml +++ b/src/controls/Action.qml @@ -1,134 +1,163 @@ /* * 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.7 +import "private" /** * An item that represents an abstract Action * * @inherit QtObject */ QtObject { id: root /** * Emitted whenever a action's checked property changes. * This usually happens at the same time as triggered. * @param checked */ signal toggled(bool checked) /** * Emitted when either the menu item or its bound action have been activated. Includes the object that triggered the event if relevant (e.g. a Button). * You shouldn't need to emit this signal, use trigger() instead. * @param source Object that triggered the event if relevant, often null */ signal triggered(QtObject source) /** * visible: bool * True (default) when the graphic representation of the action * is supposed to be visible. * It's up to the action representation to honor this property. */ property bool visible: true /** * checkable: bool * Whether action can be checked, or toggled. Defaults to false. */ property bool checkable: false /** * checked: bool * Whether the action is checked. Defaults to false. */ property bool checked: false /** * enabled: bool * Whether the action is enabled, and can be triggered. Defaults to true. */ property bool enabled: true /** * iconName: string * Sets the icon name for the action. This will pick the icon with the given name from the current theme. */ - property string iconName + property alias iconName: iconGroup.name /** * iconSource: string * Sets the icon file or resource url for the action. Defaults to the empty URL. Use this if you want a specific file rather than an icon from the theme */ - property string iconSource + property alias iconSource: iconGroup.source + + /** + * metadata for the icon, such as width/height.name and source + * * name This property holds the name of the icon to use. + * The icon will be loaded from the platform theme. + * If the icon is found in the theme, it will always be used; + * even if icon.source is also set. If the icon is not found, + * icon.source will be used instead. + * For more information on theme icons, see QIcon::fromTheme(). + * + * * source This property holds the name of the icon to use. + * The icon will be loaded as a regular image. + * If icon.name is set and refers to a valid theme icon, + * it will always be used instead of this property. + * + * * width This property holds the width of the icon. + * The icon's width will never exceed this value, + * though it will shrink when necessary. + * height This property holds the height of the icon. + * The icon's height will never exceed this value, + * though it will shrink when necessary. + * + * *color This property holds the color of the icon. + * The icon is tinted with the specified color, unless the color is set to "transparent". + */ + property ActionIconGroup icon: ActionIconGroup { + id: iconGroup + } /** * shortcut : keysequence * Shortcut bound to the action. The keysequence can be a string or a Qt standard key. */ property alias shortcut: shortcutItem.sequence /** * Text for the action. This text will show as the button text, or as title in a menu item, depending from the way the developer will choose to represent it */ property string text /** * A tooltip text to be shown when hovering the control bound to this action. Not all controls support tooltips on all platforms */ property string tooltip /** * children: list * A list of children actions. * Useful for tree-like menus * @code * Action { * text: "Tools" * Action { * text: "Action1" * } * Action { * text: "Action2" * } * } * @endcode */ default property alias children: root.__children property list __children property Shortcut __shortcut: Shortcut { property bool checked: false id: shortcutItem enabled: root.enabled onActivated: root.trigger(); } function trigger(source) { if (!enabled) { return; } root.triggered(source); if (root.checkable) { root.checked = !root.checked; root.toggled(root.checked); } } onCheckedChanged: root.toggled(root.checked); } diff --git a/src/controls/BasicListItem.qml b/src/controls/BasicListItem.qml index b652549a..3ea9f754 100644 --- a/src/controls/BasicListItem.qml +++ b/src/controls/BasicListItem.qml @@ -1,79 +1,82 @@ /* * Copyright 2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.2 /** * An item delegate for the primitive ListView component. * * It's intended to make all listviews look coherent. * It has a default icon and a label * */ AbstractListItem { id: listItem /** * string: bool * A single text label the list item will contain */ property alias label: listItem.text /** * icon: var - * A single icon that will be displayed in the list item. The icon can - * be either a QIcon, a string name of a fdo compatible name, + * A single icon that will be displayed in the list item. + * The icon can be a grouped property with name,size,color etc, as QtQuickControls2 icons are defined. + * The icon can also be either a QIcon, a string name of a fdo compatible name, * or any url accepted by the Image element. */ - property alias icon: iconItem.source + property var icon /** * reserveSpaceForIcon: bool * If true, even when there is no icon the space will be reserved for it * It's useful in layouts where only some entries have an icon, * having the text all horizontally aligned */ property alias reserveSpaceForIcon: iconItem.visible default property alias _basicDefault: layout.children RowLayout { id: layout spacing: Units.smallSpacing*2 property bool indicateActiveFocus: listItem.pressed || Settings.isMobile || listItem.activeFocus || (listItem.ListView.view ? listItem.ListView.view.activeFocus : false) Icon { id: iconItem + source: listItem.icon && listItem.icon.hasOwnProperty && listItem.icon.hasOwnProperty("name") ? listItem.icon.name : listItem.icon Layout.minimumHeight: Units.iconSizes.smallMedium Layout.maximumHeight: Layout.minimumHeight Layout.minimumWidth: height selected: layout.indicateActiveFocus && (listItem.checked || listItem.pressed) + color: listItem.icon && listItem.icon.color && listItem.icon.color.a > 0 ? listItem.icon.color : Qt.rgba(0, 0, 0, 0) } QQC2.Label { id: labelItem text: listItem.text Layout.fillWidth: true color: layout.indicateActiveFocus && (listItem.checked || listItem.pressed) ? listItem.activeTextColor : listItem.textColor elide: Text.ElideRight font: listItem.font } } } diff --git a/src/controls/ContextDrawer.qml b/src/controls/ContextDrawer.qml index d101c0a0..740b72ff 100644 --- a/src/controls/ContextDrawer.qml +++ b/src/controls/ContextDrawer.qml @@ -1,157 +1,157 @@ /* * 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.2 import "templates/private" /** * A drawer specialization that will show a list of actions that are * specific of the current page shown by the application * * Example usage: * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * [...] * } * @endcode * * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.Page { * [...] * contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * @endcode * * @inherit AbstractDrawer */ OverlayDrawer { id: root /** * title: string * A title for the action list that will be shown to the user when opens the drawer */ property string title: qsTr("Actions") /** * actions: list * This can be any type of object that a ListView can accept as model. * It expects items compatible with either QAction or Kirigami Action */ property var actions: pageStack.layers.depth > 1 ? pageStack.layers.currentItem.contextualActions : (pageStack.currentItem ? pageStack.currentItem.contextualActions : null) enabled: menu.count > 0 edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge drawerOpen: false //list items go to edges, have their own padding leftPadding: 0 rightPadding: 0 bottomPadding: 0 handleVisible: applicationWindow == undefined ? false : applicationWindow().controlsVisible contentItem: ScrollView { //this just to create the attached property Theme.inherit: true implicitWidth: Units.gridUnit * 20 ListView { id: menu interactive: contentHeight > height model: { if (typeof root.actions == "undefined") { return null; } if (root.actions.length == 0) { return null; } else { return root.actions[0].text !== undefined && root.actions[0].trigger !== undefined ? root.actions : root.actions[0]; } } topMargin: menu.height - menu.contentHeight header: Item { height: heading.height width: menu.width Heading { id: heading anchors { left: parent.left right: parent.right margins: Units.largeSpacing } elide: Text.ElideRight level: 2 text: root.title } } delegate: BasicListItem { checked: modelData.checked - icon: modelData.iconName + icon: modelData.icon supportsMouseEvents: true separatorVisible: false label: model ? (model.tooltip ? model.tooltip : model.text) : (modelData.tooltip ? modelData.tooltip : modelData.text) enabled: model ? model.enabled : modelData.enabled visible: model ? model.visible : modelData.visible opacity: enabled ? 1.0 : 0.6 onClicked: { if (modelData && modelData.trigger !== undefined) { modelData.trigger(); // assume the model is a list of QAction or Action } else if (menu.model.length > index) { menu.model[index].trigger(); } else { console.warning("Don't know how to trigger the action") } root.drawerOpen = false; } } } } } diff --git a/src/controls/GlobalDrawer.qml b/src/controls/GlobalDrawer.qml index bd184010..11f3109f 100644 --- a/src/controls/GlobalDrawer.qml +++ b/src/controls/GlobalDrawer.qml @@ -1,446 +1,446 @@ /* * 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.Templates 2.0 as T2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.2 import "private" import "templates/private" /** * A drawer specialization intended for the global actions of the application * valid regardless of the application state (think about the menubar * of a desktop application). * * Example usage: * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [ * Kirigami.Action { * text: "View" * iconName: "view-list-icons" * Kirigami.Action { * text: "action 1" * } * Kirigami.Action { * text: "action 2" * } * Kirigami.Action { * text: "action 3" * } * }, * Kirigami.Action { * text: "Sync" * iconName: "folder-sync" * } * ] * } * [...] * } * @endcode * */ OverlayDrawer { id: root edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge /** * title: string * A title to be displayed on top of the drawer */ 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 /** * bannerImageSource: string * An image to be used as background for the title and icon for * a decorative purpose. * It accepts any url format supported by Image */ property alias bannerImageSource: bannerImage.source /** * actions: list * The list of actions can be nested having a tree structure. * A tree depth bigger than 2 is discouraged. * * Example usage: * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [ * Kirigami.Action { * text: "View" * iconName: "view-list-icons" * Kirigami.Action { * text: "action 1" * } * Kirigami.Action { * text: "action 2" * } * Kirigami.Action { * text: "action 3" * } * }, * Kirigami.Action { * text: "Sync" * iconName: "folder-sync" * } * ] * } * [...] * } * @endcode */ property list actions /** * content: list default property * Any random Item can be instantiated inside the drawer and * will be displayed underneath the actions list. * * Example usage: * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [...] * Button { * text: "Button" * onClicked: //do stuff * } * } * [...] * } * @endcode */ default property alias content: mainContent.data /** * topContent: list default property * Items that will be instantiated inside the drawer and * will be displayed on top of the actions list. * * Example usage: * @code * import org.kde.kirigami 2.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [...] * topContent: [Button { * text: "Button" * onClicked: //do stuff * }] * } * [...] * } * @endcode */ property alias topContent: topContent.data /** * resetMenuOnTriggered: bool * * On the actions menu, whenever a leaf action is triggered, the menu * will reset to its parent. */ property bool resetMenuOnTriggered: true /** * currentSubMenu: Action * * Points to the action acting as a submenu */ readonly property Action currentSubMenu: stackView.currentItem ? stackView.currentItem.current: null /** * Notifies that the banner has been clicked */ signal bannerClicked() /** * Reverts the menu back to its initial state */ function resetMenu() { stackView.pop(stackView.get(0, T2.StackView.DontLoad)); if (root.modal) { root.drawerOpen = false; } } rightPadding: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : Units.smallSpacing contentItem: ScrollView { id: scrollView //ensure the attached property exists Theme.inherit: true anchors.fill: parent implicitWidth: Math.min (Units.gridUnit * 20, root.parent.width * 0.8) horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff Flickable { id: mainFlickable contentWidth: width contentHeight: mainColumn.Layout.minimumHeight ColumnLayout { id: mainColumn width: mainFlickable.width spacing: 0 height: Math.max(root.height, Layout.minimumHeight) Image { 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 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 != "" anchors { left: parent.left right: parent.right 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 { id: topContent spacing: 0 Layout.alignment: Qt.AlignHCenter Layout.leftMargin: root.leftPadding Layout.rightMargin: root.rightPadding Layout.bottomMargin: Units.smallSpacing Layout.topMargin: root.topPadding Layout.fillWidth: true Layout.fillHeight: true //NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient //as items are added only after this column creation Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding visible: children.length > 0 && childrenRect.height > 0 } T2.StackView { id: stackView Layout.fillWidth: true Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0 Layout.maximumHeight: Layout.minimumHeight initialItem: menuComponent //NOTE: it's important those are NumberAnimation and not XAnimators // as while the animation is running the drawer may close, and //the animator would stop when not drawing see BUG 381576 popEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } popExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: 400; easing.type: Easing.OutCubic } } pushEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } pushExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.OutCubic } } replaceEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } replaceExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.OutCubic } } } Item { Layout.fillWidth: true Layout.fillHeight: root.actions.length>0 Layout.minimumHeight: Units.smallSpacing } ColumnLayout { id: mainContent Layout.alignment: Qt.AlignHCenter Layout.leftMargin: root.leftPadding Layout.rightMargin: root.rightPadding Layout.fillWidth: true Layout.fillHeight: true //NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient //as items are added only after this column creation Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding visible: children.length > 0 } Item { Layout.minimumWidth: Units.smallSpacing Layout.minimumHeight: root.bottomPadding } Component { id: menuComponent ColumnLayout { spacing: 0 property alias model: actionsRepeater.model property Action current property int level: 0 Layout.maximumHeight: Layout.minimumHeight BasicListItem { visible: level > 0 supportsMouseEvents: true icon: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic") label: qsTr("Back") separatorVisible: false onClicked: stackView.pop() } Repeater { id: actionsRepeater model: actions delegate: BasicListItem { id: listItem supportsMouseEvents: true checked: modelData.checked - icon: modelData.iconName + icon: modelData.icon label: modelData.text separatorVisible: false visible: model ? model.visible || model.visible===undefined : modelData.visible enabled: model ? model.enabled : modelData.enabled opacity: enabled ? 1.0 : 0.3 Icon { isMask: true anchors { verticalCenter: contentItem.verticalCenter right: contentItem.right rightMargin: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : 0 } height: Units.iconSizes.smallMedium selected: listItem.checked || listItem.pressed width: height source: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic") visible: modelData.children!==undefined && modelData.children.length > 0 } onClicked: { modelData.trigger(); if (modelData.children!==undefined && modelData.children.length > 0) { stackView.push(menuComponent, {model: modelData.children, level: level + 1, current: modelData }); } else if (root.resetMenuOnTriggered) { root.resetMenu(); } checked = Qt.binding(function() { return modelData.checked }); } } } } } } } } } diff --git a/src/controls/ToolBarApplicationHeader.qml b/src/controls/ToolBarApplicationHeader.qml index 76926ab6..9b7033f7 100644 --- a/src/controls/ToolBarApplicationHeader.qml +++ b/src/controls/ToolBarApplicationHeader.qml @@ -1,173 +1,173 @@ /* * 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 Controls import QtQuick.Layouts 1.2 import "private" import org.kde.kirigami 2.2 /** * This Application header represents a toolbar that * will display the actions of the current page. * Both Contextual actions and the main, left and right actions */ ApplicationHeader { id: header preferredHeight: 38 maximumHeight: preferredHeight headerStyle: ApplicationHeaderStyle.Titles //FIXME: needs a property difinition to have its own type in qml property string _internal: "" pageDelegate: Item { id: delegateItem readonly property bool current: __appWindow.pageStack.currentIndex == index property Row layout //don't scroll except just the nav buttons implicitWidth: parent.parent.width - height*2 height: parent.height Row { id: layout anchors.verticalCenter: parent.verticalCenter spacing: 2 Separator { anchors.verticalCenter: parent.verticalCenter height: parent.height * 0.6 visible: index > 0 } PrivateActionToolButton { anchors.verticalCenter: parent.verticalCenter action: page && page.actions ? page.actions.left : null showText: false } PrivateActionToolButton { anchors.verticalCenter: parent.verticalCenter action: page && page.actions ? page.actions.main : null showText: false } PrivateActionToolButton { anchors.verticalCenter: parent.verticalCenter action: page && page.actions ? page.actions.right : null showText: false } Separator { anchors.verticalCenter: parent.verticalCenter height: parent.height * 0.6 visible: page && page.actions && (page.actions.left || page.actions.main || page.actions.right) } Row { id: contextActionsContainer property var overflowSet: [] Repeater { id: repeater model: page && page.actions.contextualActions ? page.actions.contextualActions : null delegate: PrivateActionToolButton { id: actionDelegate anchors.verticalCenter: parent.verticalCenter action: modelData property bool fits: x+contextActionsContainer.x+layout.x+width < delegateItem.width - moreButton.width onFitsChanged: updateOverflowSet() function updateOverflowSet() { var index = contextActionsContainer.overflowSet.findIndex(function(act){ return act == modelData}); if ((fits || !modelData.visible) && index > -1) { contextActionsContainer.overflowSet.splice(index, 1); } else if (!fits && modelData.visible && index == -1) { contextActionsContainer.overflowSet.push(modelData); } contextActionsContainer.overflowSetChanged(); } visible: modelData.visible && fits Connections { target: modelData onVisibleChanged: actionDelegate.updateOverflowSet(); } Component.onCompleted: { actionDelegate.updateOverflowSet(); } } } } } Heading { id: heading anchors.verticalCenter: parent.verticalCenter visible: layout.width <= 0 opacity: delegateItem.current ? 1 : 0.4 color: Theme.textColor elide: Text.ElideRight text: page ? page.title : "" font.pointSize: Math.max(1, (parent.height / 1.6) / Units.devicePixelRatio) } Controls.ToolButton { id: moreButton anchors { right: parent.right verticalCenter: parent.verticalCenter } Icon { anchors.fill: parent source: "overflow-menu" anchors.margins: 4 } checkable: true checked: menu.visible visible: contextActionsContainer.overflowSet.length > 0; onClicked: menu.open() Controls.Menu { id: menu y: moreButton.height x: -width + moreButton.width Repeater { model: page && page.actions.contextualActions ? page.actions.contextualActions : null delegate: BasicListItem { text: modelData ? modelData.text : "" - icon: modelData.iconName + icon: modelData.icon checkable: modelData.checkable checked: modelData.checked onClicked: { modelData.trigger(); menu.visible = false; } separatorVisible: false backgroundColor: "transparent" visible: contextActionsContainer.overflowSet.findIndex(function(act) { return act == modelData}) > -1 && modelData.visible enabled: modelData.enabled } } } } } } diff --git a/src/controls/private/ActionButton.qml b/src/controls/private/ActionButton.qml index 7cffe9cc..d8bf03d5 100644 --- a/src/controls/private/ActionButton.qml +++ b/src/controls/private/ActionButton.qml @@ -1,472 +1,475 @@ /* * 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 QtQuick.Controls 2.0 as Controls import QtGraphicalEffects 1.0 import org.kde.kirigami 2.2 import "../templates/private" Item { id: root Theme.colorSet: Theme.Button Theme.inherit: false anchors { left: parent.left right: parent.right bottom: parent.bottom bottomMargin: root.page.footer ? root.page.footer.height : 0 } //smallSpacing for the shadow height: button.height + Units.smallSpacing clip: true readonly property Page page: root.parent.page //either Action or QAction should work here readonly property QtObject action: root.page ? root.page.mainAction : null readonly property QtObject leftAction: root.page ? root.page.leftAction : null readonly property QtObject rightAction: root.page ? root.page.rightAction : null transform: Translate { id: translateTransform y: mouseArea.internalVisibility ? 0 : button.height Behavior on y { NumberAnimation { duration: Units.longDuration easing.type: mouseArea.internalVisibility == true ? Easing.InQuad : Easing.OutQuad } } } onWidthChanged: button.x = root.width/2 - button.width/2 Item { id: button x: root.width/2 - button.width/2 anchors { bottom: parent.bottom bottomMargin: Units.smallSpacing } implicitWidth: implicitHeight + Units.iconSizes.smallMedium*2 + Units.gridUnit implicitHeight: Units.iconSizes.medium + Units.largeSpacing onXChanged: { if (mouseArea.pressed || edgeMouseArea.pressed || fakeContextMenuButton.pressed) { if (globalDrawer && globalDrawer.enabled && globalDrawer.modal) { globalDrawer.peeking = true; globalDrawer.visible = true; globalDrawer.position = Math.min(1, Math.max(0, (x - root.width/2 + button.width/2)/globalDrawer.contentItem.width + mouseArea.drawerShowAdjust)); } if (contextDrawer && contextDrawer.enabled && contextDrawer.modal) { contextDrawer.peeking = true; contextDrawer.visible = true; contextDrawer.position = Math.min(1, Math.max(0, (root.width/2 - button.width/2 - x)/contextDrawer.contentItem.width + mouseArea.drawerShowAdjust)); } } } MouseArea { id: mouseArea anchors.fill: parent visible: action != null || leftAction != null || rightAction != null property bool internalVisibility: (applicationWindow === undefined || (applicationWindow().controlsVisible && applicationWindow().height > root.height*2)) && (root.action === null || root.action.visible === undefined || root.action.visible) preventStealing: true drag { target: button //filterChildren: true axis: Drag.XAxis minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } property var downTimestamp; property int startX property int startMouseY property real drawerShowAdjust property bool buttonPressedUnderMouse: false property bool leftButtonPressedUnderMouse: false property bool rightButtonPressedUnderMouse: false onPressed: { //search if we have a page to set to current if (applicationWindow !== undefined && applicationWindow().pageStack.currentIndex !== undefined && root.page.parent.level !== undefined) { //search the button parent's parent, that is the page parent //this will make the context drawer open for the proper page applicationWindow().pageStack.currentIndex = root.page.parent.level; } downTimestamp = (new Date()).getTime(); startX = button.x + button.width/2; startMouseY = mouse.y; drawerShowAdjust = 0; buttonPressedUnderMouse = mouse.x > buttonGraphics.x && mouse.x < buttonGraphics.x + buttonGraphics.width; leftButtonPressedUnderMouse = !buttonPressedUnderMouse && leftAction && mouse.x < buttonGraphics.x; rightButtonPressedUnderMouse = !buttonPressedUnderMouse && rightAction && mouse.x > buttonGraphics.x + buttonGraphics.width; } onReleased: { if (globalDrawer) globalDrawer.peeking = false; if (contextDrawer) contextDrawer.peeking = false; //pixel/second var x = button.x + button.width/2; var speed = ((x - startX) / ((new Date()).getTime() - downTimestamp) * 1000); drawerShowAdjust = 0; //project where it would be a full second in the future if (globalDrawer && globalDrawer.modal && x + speed > Math.min(root.width/4*3, root.width/2 + globalDrawer.contentItem.width/2)) { globalDrawer.open(); contextDrawer.close(); } else if (contextDrawer && x + speed < Math.max(root.width/4, root.width/2 - contextDrawer.contentItem.width/2)) { if (contextDrawer && contextDrawer.modal) { contextDrawer.open(); } if (globalDrawer && globalDrawer.modal) { globalDrawer.close(); } } else { if (globalDrawer && globalDrawer.modal) { globalDrawer.close(); } if (contextDrawer && contextDrawer.modal) { contextDrawer.close(); } } //Don't rely on native onClicked, but fake it here: //Qt.startDragDistance is not adapted to devices dpi in case //of Android, so consider the button "clicked" when: //*the button has been dragged less than a gridunit //*the finger is still on the button if (Math.abs((button.x + button.width/2) - startX) < Units.gridUnit && mouse.y > 0) { var action; if (buttonPressedUnderMouse) { action = root.action; } else if (leftButtonPressedUnderMouse) { action = root.leftAction; } else if (rightButtonPressedUnderMouse) { action = root.rightAction; } if (!action) { return; } //if an action has been assigned, trigger it if (action && action.trigger) { action.trigger(); } } } onPositionChanged: { drawerShowAdjust = Math.min(0.3, Math.max(0, (startMouseY - mouse.y)/(Units.gridUnit*15))); button.xChanged(); } onPressAndHold: { var action; if (buttonPressedUnderMouse) { action = root.action; } else if (leftButtonPressedUnderMouse) { action = root.leftAction; } else if (rightButtonPressedUnderMouse) { action = root.rightAction; } if (!action) { return; } //if an action has been assigned, show a message like a tooltip if (action && action.text) { showPassiveNotification(action.text); } } Connections { target: globalDrawer onPositionChanged: { if ( globalDrawer && globalDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) { button.x = globalDrawer.contentItem.width * globalDrawer.position + root.width/2 - button.width/2; } } } Connections { target: contextDrawer onPositionChanged: { if (contextDrawer && contextDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) { button.x = root.width/2 - button.width/2 - contextDrawer.contentItem.width * contextDrawer.position; } } } Item { id: background anchors { fill: parent } Rectangle { id: buttonGraphics radius: width/2 anchors.centerIn: parent height: parent.height - Units.smallSpacing*2 width: height visible: root.action readonly property bool pressed: root.action && ((mouseArea.buttonPressedUnderMouse && mouseArea.pressed) || root.action.checked) - color: pressed ? Qt.darker(Theme.highlightColor, 1.3) : Theme.highlightColor + property color baseColor: root.action && root.action.icon && root.action.icon.color && root.action.icon.color != undefined && root.action.icon.color.a > 0 ? root.action.icon.color : Theme.highlightColor + color: pressed ? Qt.darker(baseColor, 1.3) : baseColor Icon { id: icon anchors.centerIn: parent width: Units.iconSizes.smallMedium height: width source: root.action && root.action.iconName ? root.action.iconName : "" selected: true } Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Behavior on x { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } //left button Rectangle { id: leftButtonGraphics z: -1 anchors { left: parent.left //verticalCenter: parent.verticalCenter bottom: parent.bottom bottomMargin: Units.smallSpacing } radius: Units.devicePixelRatio*2 height: Units.iconSizes.smallMedium + Units.smallSpacing * 2 width: height + (root.action ? Units.gridUnit*2 : 0) visible: root.leftAction readonly property bool pressed: root.leftAction && ((mouseArea.leftButtonPressedUnderMouse && mouseArea.pressed) || root.leftAction.checked) - color: pressed ? Theme.highlightColor : Theme.backgroundColor + property color baseColor: root.leftAction && root.leftAction.icon && root.leftAction.icon.color && root.leftAction.icon.color != undefined && root.leftAction.icon.color.a > 0 ? root.leftAction.icon.color : Theme.highlightColor + color: pressed ? baseColor : Theme.backgroundColor Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Icon { source: root.leftAction && root.leftAction.iconName ? root.leftAction.iconName : "" width: Units.iconSizes.smallMedium height: width selected: leftButtonGraphics.pressed anchors { left: parent.left verticalCenter: parent.verticalCenter margins: Units.smallSpacing * 2 } } } //right button Rectangle { id: rightButtonGraphics z: -1 anchors { right: parent.right //verticalCenter: parent.verticalCenter bottom: parent.bottom bottomMargin: Units.smallSpacing } radius: Units.devicePixelRatio*2 height: Units.iconSizes.smallMedium + Units.smallSpacing * 2 width: height + (root.action ? Units.gridUnit*2 : 0) visible: root.rightAction readonly property bool pressed: root.rightAction && ((mouseArea.rightButtonPressedUnderMouse && mouseArea.pressed) || root.rightAction.checked) - color: pressed ? Theme.highlightColor : Theme.backgroundColor + property color baseColor: root.rightAction && root.rightAction.icon && root.rightAction.icon.color && root.rightAction.icon.color != undefined && root.rightAction.icon.color.a > 0 ? root.rightAction.icon.color : Theme.highlightColor + color: pressed ? baseColor : Theme.backgroundColor Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Icon { source: root.rightAction && root.rightAction.iconName ? root.rightAction.iconName : "" width: Units.iconSizes.smallMedium height: width selected: rightButtonGraphics.pressed anchors { right: parent.right verticalCenter: parent.verticalCenter margins: Units.smallSpacing * 2 } } } } DropShadow { anchors.fill: background horizontalOffset: 0 verticalOffset: Units.devicePixelRatio radius: Units.gridUnit /2 samples: 16 color: Qt.rgba(0, 0, 0, mouseArea.pressed ? 0.6 : 0.4) source: background } } } MouseArea { id: fakeContextMenuButton anchors { right: edgeMouseArea.right bottom: edgeMouseArea.bottom } drag { target: button axis: Drag.XAxis minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } visible: root.page.actions && root.page.actions.contextualActions.length > 0 && (applicationWindow === undefined || applicationWindow().wideScreen) //using internal pagerow api && (root.page && root.page.parent ? root.page.parent.level < applicationWindow().pageStack.depth-1 : false) width: Units.iconSizes.medium + Units.smallSpacing*2 height: width Item { anchors { fill:parent margins: -Units.gridUnit } DropShadow { anchors.fill: handleGraphics horizontalOffset: 0 verticalOffset: Units.devicePixelRatio radius: Units.gridUnit /2 samples: 16 color: Qt.rgba(0, 0, 0, fakeContextMenuButton.pressed ? 0.6 : 0.4) source: handleGraphics } Rectangle { id: handleGraphics anchors.centerIn: parent color: fakeContextMenuButton.pressed ? Theme.highlightColor : Theme.backgroundColor width: Units.iconSizes.smallMedium + Units.smallSpacing * 2 height: width radius: Units.devicePixelRatio Icon { anchors.centerIn: parent width: Units.iconSizes.smallMedium selected: fakeContextMenuButton.pressed height: width source: "overflow-menu" } Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } } onPressed: { mouseArea.onPressed(mouse) } onReleased: { if (globalDrawer) { globalDrawer.peeking = false; } if (contextDrawer) { contextDrawer.peeking = false; } var pos = root.mapFromItem(fakeContextMenuButton, mouse.x, mouse.y); if (contextDrawer) { if (pos.x < root.width/2) { contextDrawer.open(); } else if (contextDrawer.drawerOpen && mouse.x > 0 && mouse.x < width) { contextDrawer.close(); } } if (globalDrawer) { if (globalDrawer.position > 0.5) { globalDrawer.open(); } else { globalDrawer.close(); } } if (containsMouse && (!globalDrawer || !globalDrawer.drawerOpen || !globalDrawer.modal) && (!contextDrawer || !contextDrawer.drawerOpen || !contextDrawer.modal)) { contextMenu.visible = !contextMenu.visible; } } Controls.Menu { id: contextMenu x: parent.width - width y: -height Repeater { model: root.page.actions.contextualActions delegate: BasicListItem { text: model.text icon: model.iconName backgroundColor: "transparent" visible: model.visible enabled: modelData.enabled checkable: modelData.checkable checked: modelData.checked separatorVisible: false onClicked: { modelData.trigger(); contextMenu.visible = false; } } } } } MouseArea { id: edgeMouseArea z:99 anchors { left: parent.left right: parent.right bottom: parent.bottom } drag { target: button //filterChildren: true axis: Drag.XAxis minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } height: Units.smallSpacing * 3 onPressed: mouseArea.onPressed(mouse) onPositionChanged: mouseArea.positionChanged(mouse) onReleased: mouseArea.released(mouse) } } diff --git a/src/controls/private/ActionIconGroup.qml b/src/controls/private/ActionIconGroup.qml new file mode 100644 index 00000000..19a5ea19 --- /dev/null +++ b/src/controls/private/ActionIconGroup.qml @@ -0,0 +1,29 @@ +/* + * 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 QtQml 2.1 + +QtObject { + property string name + property string source + property int width + property int height + property color color: Qt.rgba(0, 0, 0, 0) +} + diff --git a/src/controls/private/PrivateActionToolButton.qml b/src/controls/private/PrivateActionToolButton.qml index 4488931f..4a9c68d9 100644 --- a/src/controls/private/PrivateActionToolButton.qml +++ b/src/controls/private/PrivateActionToolButton.qml @@ -1,76 +1,77 @@ /* * 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.6 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.2 Controls.ToolButton { id: control implicitWidth: showText ? Math.max(background.implicitWidth, layout.implicitWidth + 16) : height implicitHeight: background.implicitHeight hoverEnabled: true property Action action property bool showText: true //we need our own text delegate text: "" checkable: action && action.checkable checked: action && action.checked enabled: action && action.enabled opacity: enabled ? 1 : 0.4 visible: action && action.visible onClicked: { if (action) { action.trigger(); } } flat: true contentItem: MouseArea { hoverEnabled: true onPressed: mouse.accepted = false Theme.colorSet: checked ? Theme.Selection : Theme.Window Theme.inherit: false RowLayout { id: layout anchors.centerIn: parent Icon { Layout.minimumWidth: 22 Layout.minimumHeight: 22 - source: control.action ? control.action.iconName : "" + source: control.action ? (control.action.icon ? control.action.icon.name : control.action.iconName) : "" visible: control.action && control.action.iconName != "" + color: control.action && control.action.icon && control.action.icon.color.a > 0 ? control.action.icon.color : Qt.rgba(0, 0, 0, 0) } - Label { + Controls.Label { text: action ? action.text : "" visible: control.showText } } } Controls.ToolTip { visible: control.hovered text: action ? (action.tooltip.length ? action.tooltip : action.text) : "" delay: 1000 timeout: 5000 y: control.height } }