Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,408 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
//TODO: Kf6: move somewhere else which can depend from KAboutData?
/**
* @brief An about item that displays the about data
*
* Allows to show the copyright notice of the application
* together with the contributors and some information of which platform it's
* running on.
*
* @since 5.87
* @since org.kde.kirigami 2.19
*/
Item {
id: aboutItem
/**
* @brief This property holds an object with the same shape as KAboutData.
*
* Example usage:
* @code{json}
* aboutData: {
"displayName" : "KirigamiApp",
"productName" : "kirigami/app",
"componentName" : "kirigamiapp",
"shortDescription" : "A Kirigami example",
"homepage" : "",
"bugAddress" : "submit@bugs.kde.org",
"version" : "5.14.80",
"otherText" : "",
"authors" : [
{
"name" : "...",
"task" : "",
"emailAddress" : "somebody@kde.org",
"webAddress" : "",
"ocsUsername" : ""
}
],
"credits" : [],
"translators" : [],
"licenses" : [
{
"name" : "GPL v2",
"text" : "long, boring, license text",
"spdx" : "GPL-2.0"
}
],
"copyrightStatement" : "© 2010-2018 Plasma Development Team",
"desktopFileName" : "org.kde.kirigamiapp"
}
@endcode
*
* @see KAboutData
*/
property var aboutData
/**
* @brief This property holds a link to a "Get Involved" page.
*
* default: `"https://community.kde.org/Get_Involved" when application id starts with "org.kde.", otherwise it is empty.`
*/
property url getInvolvedUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://community.kde.org/Get_Involved" : ""
/**
* @brief This property holds a link to a "Donate" page.
*
* default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
*/
property url donateUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://kde.org/community/donations" : ""
/** @internal */
property bool _usePageStack: false
/**
* @see org::kde::kirigami::FormLayout::wideMode
* @property bool wideMode
*/
property alias wideMode: form.wideMode
/** @internal */
default property alias _content: form.data
// if aboutData is a native KAboutData object, avatarUrl should be a proper url instance,
// otherwise if it was defined as a string in pure JavaScript it should work too.
readonly property bool __hasAvatars: aboutItem.aboutData.authors.some(__hasAvatar)
function __hasAvatar(person): bool {
return typeof person.avatarUrl !== "undefined"
&& person.avatarUrl.toString().length > 0;
}
/**
* @brief This property controls whether to load avatars by URL.
*
* If set to false, a fallback "user" icon will be displayed.
*
* default: ``false``
*/
property bool loadAvatars: false
implicitHeight: form.implicitHeight
implicitWidth: form.implicitWidth
Component {
id: personDelegate
RowLayout {
id: delegate
// type: KAboutPerson | { name?, task?, emailAddress?, webAddress?, avatarUrl? }
required property var modelData
property bool hasAvatar: aboutItem.__hasAvatar(modelData)
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing * 2
Kirigami.Icon {
id: avatarIcon
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: implicitWidth
fallback: "user"
source: {
if (delegate.hasAvatar && aboutItem.loadAvatars) {
// Appending to the params of the url does not work, thus the search is set
const url = new URL(modelData.avatarUrl);
const params = new URLSearchParams(url.search);
params.append("s", width);
url.search = params.toString();
return url;
} else {
return "user"
}
}
visible: status !== Kirigami.Icon.Loading
}
// So it's clear that something is happening while avatar images are loaded
QQC2.BusyIndicator {
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: implicitWidth
visible: avatarIcon.status === Kirigami.Icon.Loading
running: visible
}
QQC2.Label {
Layout.fillWidth: true
readonly property bool withTask: typeof(modelData.task) !== "undefined" && modelData.task.length > 0
text: withTask ? qsTr("%1 (%2)").arg(modelData.name).arg(modelData.task) : modelData.name
wrapMode: Text.WordWrap
}
QQC2.ToolButton {
visible: typeof(modelData.emailAddress) !== "undefined" && modelData.emailAddress.length > 0
icon.name: "mail-sent"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: qsTr("Send an email to %1").arg(modelData.emailAddress)
onClicked: Qt.openUrlExternally("mailto:%1".arg(modelData.emailAddress))
}
QQC2.ToolButton {
visible: typeof(modelData.webAddress) !== "undefined" && modelData.webAddress.length > 0
icon.name: "globe"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: (typeof(modelData.webAddress) === "undefined" && modelData.webAddress.length > 0) ? "" : modelData.webAddress
onClicked: Qt.openUrlExternally(modelData.webAddress)
}
}
}
Kirigami.FormLayout {
id: form
anchors.fill: parent
GridLayout {
columns: 2
Layout.fillWidth: true
Kirigami.Icon {
Layout.rowSpan: 3
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.preferredWidth: height
Layout.maximumWidth: aboutItem.width / 3;
Layout.rightMargin: Kirigami.Units.largeSpacing
source: Kirigami.Settings.applicationWindowIcon || aboutItem.aboutData.programLogo || aboutItem.aboutData.programIconName || aboutItem.aboutData.componentName
}
Kirigami.Heading {
Layout.fillWidth: true
text: aboutItem.aboutData.displayName + " " + aboutItem.aboutData.version
wrapMode: Text.WordWrap
}
Kirigami.Heading {
Layout.fillWidth: true
level: 2
wrapMode: Text.WordWrap
text: aboutItem.aboutData.shortDescription
}
RowLayout {
spacing: Kirigami.Units.largeSpacing * 2
UrlButton {
text: qsTr("Get Involved")
url: aboutItem.getInvolvedUrl
visible: url.toString().length > 0
}
UrlButton {
text: qsTr("Donate")
url: aboutItem.donateUrl
visible: url.toString().length > 0
}
UrlButton {
readonly property string theUrl: {
if (aboutItem.aboutData.bugAddress !== "submit@bugs.kde.org") {
return aboutItem.aboutData.bugAddress
}
const elements = aboutItem.aboutData.productName.split('/');
let url = `https://bugs.kde.org/enter_bug.cgi?format=guided&product=${elements[0]}&version=${aboutItem.aboutData.version}`;
if (elements.length === 2) {
url += "&component=" + elements[1];
}
return url;
}
text: qsTr("Report a Bug")
url: theUrl
visible: theUrl.toString().length > 0
}
}
}
Separator {
Layout.fillWidth: true
}
Kirigami.Heading {
Kirigami.FormData.isSection: true
text: qsTr("Copyright")
}
QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
text: aboutItem.aboutData.otherText
visible: text.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
text: aboutItem.aboutData.copyrightStatement
visible: text.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
UrlButton {
Layout.leftMargin: Kirigami.Units.gridUnit
url: aboutItem.aboutData.homepage
visible: url.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
OverlaySheet {
id: licenseSheet
property alias text: bodyLabel.text
contentItem: SelectableLabel {
id: bodyLabel
text: licenseSheet.text
wrapMode: Text.Wrap
}
}
Component {
id: licenseLinkButton
RowLayout {
Layout.leftMargin: Kirigami.Units.smallSpacing
QQC2.Label { text: qsTr("License:") }
LinkButton {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: modelData.name
onClicked: mouse => {
licenseSheet.text = modelData.text
licenseSheet.title = modelData.name
licenseSheet.open()
}
}
}
}
Component {
id: licenseTextItem
QQC2.Label {
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("License: %1").arg(modelData.name)
}
}
Repeater {
model: aboutItem.aboutData.licenses
delegate: _usePageStack ? licenseLinkButton : licenseTextItem
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Libraries in use")
Layout.fillWidth: true
wrapMode: Text.WordWrap
visible: Kirigami.Settings.information
}
Repeater {
model: Kirigami.Settings.information
delegate: QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.fillWidth: true
wrapMode: Text.WordWrap
id: libraries
text: modelData
}
}
Repeater {
model: aboutItem.aboutData.components
delegate: QQC2.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
Layout.leftMargin: Kirigami.Units.gridUnit
text: modelData.name + (modelData.version.length === 0 ? "" : " %1".arg(modelData.version))
}
}
Kirigami.Heading {
Layout.fillWidth: true
Kirigami.FormData.isSection: visible
text: qsTr("Authors")
wrapMode: Text.WordWrap
visible: aboutItem.aboutData.authors.length > 0
}
QQC2.CheckBox {
id: remoteAvatars
visible: aboutItem.__hasAvatars
checked: aboutItem.loadAvatars
onToggled: aboutItem.loadAvatars = checked
text: qsTr("Show author photos")
}
Repeater {
id: authorsRepeater
model: aboutItem.aboutData.authors
delegate: personDelegate
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Credits")
visible: repCredits.count > 0
}
Repeater {
id: repCredits
model: aboutItem.aboutData.credits
delegate: personDelegate
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Translators")
visible: repTranslators.count > 0
}
Repeater {
id: repTranslators
model: aboutItem.aboutData.translators
delegate: personDelegate
}
}
}
@@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
//TODO KF6: move somewhere else? kirigami addons?
/**
* @brief An "About" page that is ready to integrate in a Kirigami app.
*
* Allows to have a page that will show the copyright notice of the application
* together with the contributors and some information of which platform it's
* running on.
*
* @since 5.52
* @since org.kde.kirigami 2.6
* @inherit org::kde::kirigami::ScrollablePage
*/
Kirigami.ScrollablePage {
id: page
//BEGIN properties
/**
* @brief This property holds an object with the same shape as KAboutData.
*
* For example:
* @code{json}
* aboutData: {
"displayName" : "KirigamiApp",
"productName" : "kirigami/app",
"componentName" : "kirigamiapp",
"shortDescription" : "A Kirigami example",
"homepage" : "",
"bugAddress" : "submit@bugs.kde.org",
"version" : "5.14.80",
"otherText" : "",
"authors" : [
{
"name" : "...",
"task" : "",
"emailAddress" : "somebody@kde.org",
"webAddress" : "",
"ocsUsername" : ""
}
],
"credits" : [],
"translators" : [],
"licenses" : [
{
"name" : "GPL v2",
"text" : "long, boring, license text",
"spdx" : "GPL-2.0"
}
],
"copyrightStatement" : "© 2010-2018 Plasma Development Team",
"desktopFileName" : "org.kde.kirigamiapp"
}
@endcode
*
* @see KAboutData
* @see org::kde::kirigami::AboutItem::aboutData
* @property KAboutData aboutData
*/
property alias aboutData: aboutItem.aboutData
/**
* @brief This property holds a link to a "Get Involved" page.
*
* default: `"https://community.kde.org/Get_Involved" when your application id starts with "org.kde.", otherwise is empty`
*
* @property url getInvolvedUrl
*/
property alias getInvolvedUrl: aboutItem.getInvolvedUrl
/**
* @brief This property holds a link to a "Donate" page.
* @since 5.101
*
* default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
*/
property alias donateUrl: aboutItem.donateUrl
/**
* @brief This property controls whether to load avatars by URL.
*
* If set to false, a fallback "user" icon will be displayed.
*
* default: ``false``
*/
property alias loadAvatars: aboutItem.loadAvatars
/** @internal */
default property alias _content: aboutItem._content
//END properties
title: qsTr("About %1").arg(page.aboutData.displayName)
Kirigami.AboutItem {
id: aboutItem
wideMode: page.width >= aboutItem.implicitWidth
_usePageStack: applicationWindow().pageStack ? true : false
}
}
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import "private" as P
import "templates" as T
//TODO KF6: remove
/**
* @brief An item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away)
* It's a behavior similar to the typical mobile web browser addressbar
* the minimum, preferred and maximum heights of the item can be controlled with
* * ``minimumHeight``: default is 0, i.e. hidden
* * ``preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
* * ``maximumHeight``: default is Kirigami.Units.gridUnit * 3
*
* To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
*
* @inherit org::kde::kirigami::templates::AbstractApplicationHeader
*/
T.AbstractApplicationHeader {
id: root
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
background: Rectangle {
color: Kirigami.Theme.backgroundColor
P.EdgeShadow {
id: shadow
visible: root.separatorVisible
anchors {
right: parent.right
left: parent.left
top: parent.bottom
}
edge: Qt.TopEdge
opacity: (!root.page || !root.page.header || root.page.header.toString().indexOf("ToolBar") === -1)
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
@@ -0,0 +1,355 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "templates/private" as TP
import "templates" as KT
/**
* @brief An item that provides the features of AbstractApplicationWindow without the window itself.
*
* This allows embedding into a larger application.
* Unless you need extra flexibility it is recommended to use ApplicationItem instead.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.AbstractApplicationItem {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack: Kirigami.PageRow {
* ...
* }
* [...]
* }
* @endcode
*
* @inherit QtQuick.Item
*/
Item {
id: root
//BEGIN properties
/**
* @brief This property holds the stack used to allocate the pages and to manage the
* transitions between them.
*
* Put a container here, such as QtQuick.Controls.StackView.
*/
property Item pageStack
/**
* @brief This property holds the font for this item.
*
* default: ``Kirigami.Theme.defaultFont``
*/
property font font: Kirigami.Theme.defaultFont
/**
* @brief This property holds the locale for this item.
*/
property Locale locale
/**
* @brief This property holds an item that can be used as a menuBar for the application.
*/
property T.MenuBar menuBar
/**
* @brief This property holds an item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away).
*
* It's a behavior similar to the typical mobile web browser addressbar.
*
* The minimum, preferred and maximum heights of the item can be controlled with
*
* * ``Layout.minimumHeight``: default is 0, i.e. hidden
* * ``Layout.preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
* * ``Layout.maximumHeight``: default is Kirigami.Units.gridUnit * 3
*
* To achieve a titlebar that stays completely fixed, just set the 3 sizes as the same.
*
* @property kirigami::templates::AbstractApplicationHeader header
*/
property KT.AbstractApplicationHeader header
/**
* @brief This property holds an item that can be used as a footer for the application.
*/
property Item footer
/**
* @brief This property sets whether the standard chrome of the app is visible.
*
* These are the action button, the drawer handles and the application header.
*
* default: ``true``
*/
property bool controlsVisible: true
/**
* @brief This property holds the drawer for global actions.
*
* Thos drawer can be opened by sliding from the left screen edge
* or by dragging the ActionButton to the right.
*
* @note It is recommended to use the GlobalDrawer here.
* @property org::kde::kirigami::OverlayDrawer globalDrawer
*/
property OverlayDrawer globalDrawer
/**
* @brief This property tells us whether the application is in "widescreen" mode.
*
* This is enabled on desktops or horizontal tablets.
*
* @note Different styles can have their own logic for deciding this.
*/
property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
/**
* @brief This property holds the drawer for context-dependent actions.
*
* The drawer that will be opened by sliding from the right screen edge
* or by dragging the ActionButton to the left.
*
* @note It is recommended to use the ContextDrawer type here.
*
* The contents of the context drawer should depend from what page is
* loaded in the main pageStack
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @property org::kde::kirigami::ContextDrawer contextDrawer
*/
property OverlayDrawer contextDrawer
/**
* @brief This property holds the list of all children of this item.
* @internal
* @property list<Object> __data
*/
default property alias __data: contentItemRoot.data
/**
* @brief This property holds the Item of the main part of the Application UI.
*/
readonly property Item contentItem: Item {
id: contentItemRoot
parent: root
anchors {
fill: parent
topMargin: controlsVisible ? (root.header ? root.header.height : 0) + (root.menuBar ? root.menuBar.height : 0) : 0
bottomMargin: controlsVisible && root.footer ? root.footer.height : 0
leftMargin: root.globalDrawer && root.globalDrawer.modal === false ? root.globalDrawer.contentItem.width * root.globalDrawer.position : 0
rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.contentItem.width * root.contextDrawer.position : 0
}
}
/**
* @brief This property holds the color for the background.
*
* default: ``Kirigami.Theme.backgroundColor``
*/
property color color: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the background of the Application UI.
*/
property Item background
property alias overlay: overlayRoot
//END properties
//BEGIN functions
/**
* @brief This function shows a little passive notification at the bottom of the app window
* lasting for few seconds, with an optional action button.
*
* @param message The text message to be shown to the user.
* @param timeout How long to show the message:
* possible values: "short", "long" or the number of milliseconds
* @param actionText Text in the action button, if any.
* @param callBack A JavaScript function that will be executed when the
* user clicks the button.
*/
function showPassiveNotification(message, timeout, actionText, callBack) {
notificationsObject.showNotification(message, timeout, actionText, callBack);
}
/**
* @brief This function hides the passive notification at specified index, if any is shown.
* @param index Index of the notification to hide. Default is 0 (oldest notification).
*/
function hidePassiveNotification(index = 0) {
notificationsObject.hideNotification(index);
}
/**
* @brief This property gets application windows object anywhere in the application.
* @returns a pointer to this item.
*/
function applicationWindow() {
return root;
}
//END functions
//BEGIN signals handlers
onMenuBarChanged: {
if (menuBar) {
menuBar.parent = root.contentItem
if (menuBar instanceof T.ToolBar) {
menuBar.position = T.ToolBar.Footer
} else if (menuBar instanceof T.DialogButtonBox) {
menuBar.position = T.DialogButtonBox.Footer
}
menuBar.width = Qt.binding(() => root.contentItem.width)
// FIXME: (root.header.height ?? 0) when we can depend from 5.15
menuBar.y = Qt.binding(() => -menuBar.height - (root.header.height ? root.header.height : 0))
}
}
onHeaderChanged: {
if (header) {
header.parent = root.contentItem
if (header instanceof T.ToolBar) {
header.position = T.ToolBar.Header
} else if (header instanceof T.DialogButtonBox) {
header.position = T.DialogButtonBox.Header
}
header.width = Qt.binding(() => root.contentItem.width)
header.y = Qt.binding(() => -header.height)
}
}
onFooterChanged: {
if (footer) {
footer.parent = root.contentItem
if (footer instanceof T.ToolBar) {
footer.position = T.ToolBar.Footer
} else if (footer instanceof T.DialogButtonBox) {
footer.position = T.DialogButtonBox.Footer
}
footer.width = Qt.binding(() => root.contentItem.width)
footer.y = Qt.binding(() => root.contentItem.height)
}
}
onBackgroundChanged: {
if (background) {
background.parent = root.contentItem
background.anchors.fill = background.parent
}
}
onPageStackChanged: pageStack.parent = root.contentItem;
//END signals handlers
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
TP.PassiveNotificationsManager {
id: notificationsObject
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
z: 1
}
Item {
anchors.fill: parent
parent: root.parent || root
z: 999999
Item {
id: overlayRoot
z: -1
anchors.fill: parent
}
}
// Don't use root.overlay property here. For one, we know that in a window
// it will always be the same as T.Overlay.overlay; secondly Drawers
// don't care about being contained/parented to anything else anyway.
onGlobalDrawerChanged: {
if (globalDrawer) {
globalDrawer.parent = Qt.binding(() => visible ? T.Overlay.overlay : null);
}
}
onContextDrawerChanged: {
if (contextDrawer) {
contextDrawer.parent = Qt.binding(() => visible ? T.Overlay.overlay : null);
}
}
Window.onWindowChanged: {
if (globalDrawer) {
globalDrawer.visible = globalDrawer.drawerOpen;
}
if (contextDrawer) {
contextDrawer.visible = contextDrawer.drawerOpen;
}
}
implicitWidth: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 55
implicitHeight: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 40
visible: true
}
@@ -0,0 +1,289 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "templates/private" as TP
/**
* A window that provides some basic features needed for all apps
* Use this class only if you need a custom content for your application,
* different from the Page Row behavior recommended by the HIG and provided
* by ApplicationWindow.
* It is recommended to use ApplicationWindow instead
* @see ApplicationWindow
*
* It's usually used as a root QML component for the application.
* It provides support for a central page stack, side drawers, and
* basic support for the Android back button.
*
* Setting a width and height property on the ApplicationWindow
* will set its initial size, but it won't set it as an automatically binding.
* to resize programmatically the ApplicationWindow they need to
* be assigned again in an imperative fashion
*
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack: PageStack {
* ...
* }
* [...]
* }
* @endcode
*
* @inherit QtQuick.Controls.ApplicationWindow
*/
QQC2.ApplicationWindow {
id: root
//BEGIN properties
/**
* @brief This property holds the stack used to allocate the pages and to manage the
* transitions between them.
*
* Put a container here, such as QtQuick.Controls.StackView.
*/
property Item pageStack
/**
* @brief This property sets whether the standard chrome of the app is visible.
*
* These are the action button, the drawer handles, and the application header.
*
* default: ``true``
*/
property bool controlsVisible: true
/**
* @brief This property holds the drawer for global actions.
*
* This drawer can be opened by sliding from the left screen edge
* or by dragging the ActionButton to the right.
*
* @note It is recommended to use the GlobalDrawer here.
* @property org::kde::kirigami::OverlayDrawer globalDrawer
*/
property OverlayDrawer globalDrawer
/**
* @brief This property tells whether the application is in "widescreen" mode.
*
* This is enabled on desktops or horizontal tablets.
*
* @note Different styles can have their own logic for deciding this.
*/
property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
/**
* @brief This property holds the drawer for context-dependent actions.
*
* The drawer that will be opened by sliding from the right screen edge
* or by dragging the ActionButton to the left.
*
* @note It is recommended to use the ContextDrawer type here.
*
* The contents of the context drawer should depend from what page is
* loaded in the main pageStack
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @property org::kde::kirigami::ContextDrawer contextDrawer
*/
property OverlayDrawer contextDrawer
/**
* Effectively the same as T.Overlay.overlay
*/
readonly property Item overlay: T.Overlay.overlay
/**
* This property holds a standard action that will quit the application when triggered.
* Its properties have the following values:
*
* @code
* Action {
* text: "Quit"
* icon.name: "application-exit-symbolic"
* shortcut: StandardKey.Quit
* // ...
* }
* @endcode
* @since 5.76
*/
readonly property Kirigami.Action quitAction: Kirigami.Action {
text: qsTr("Quit")
icon.name: "application-exit";
shortcut: StandardKey.Quit
onTriggered: source => root.close();
}
//END properties
//BEGIN functions
/**
* @brief This function shows a little passive notification at the bottom of the app window
* lasting for few seconds, with an optional action button.
*
* @param message The text message to be shown to the user.
* @param timeout How long to show the message:
* possible values: "short", "long" or the number of milliseconds
* @param actionText Text in the action button, if any.
* @param callBack A JavaScript function that will be executed when the
* user clicks the button.
*/
function showPassiveNotification(message, timeout, actionText, callBack) {
notificationsObject.showNotification(message, timeout, actionText, callBack);
}
/**
* @brief This function hides the passive notification at specified index, if any is shown.
* @param index Index of the notification to hide. Default is 0 (oldest notification).
*/
function hidePassiveNotification(index = 0) {
notificationsObject.hideNotification(index);
}
/**
* @brief This function returns application window's object anywhere in the application.
* @returns a pointer to this application window
* can be used anywhere in the application.
*/
function applicationWindow() {
return root;
}
//END functions
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
color: Kirigami.Theme.backgroundColor
TP.PassiveNotificationsManager {
id: notificationsObject
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
z: 1
}
contentItem.z: 1
contentItem.anchors.left: contentItem.parent.left
contentItem.anchors.right: contentItem.parent.right
contentItem.anchors.topMargin: root.wideScreen && header && controlsVisible ? header.height : 0
contentItem.anchors.leftMargin: root.globalDrawer && root.globalDrawer.modal === false && (!root.pageStack || root.pageStack.leftSidebar !== root.globalDrawer) ? root.globalDrawer.width * root.globalDrawer.position : 0
contentItem.anchors.rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.width * root.contextDrawer.position : 0
Binding {
target: root.header
property: "x"
value: -contentItem.x
}
Binding {
target: root.footer
property: "x"
value: -contentItem.x
}
// Don't use root.overlay property here. For one, we know that in a window
// it will always be the same as T.Overlay.overlay; secondly Drawers
// don't care about being contained/parented to anything else anyway.
onGlobalDrawerChanged: {
if (globalDrawer) {
globalDrawer.parent = Qt.binding(() => T.Overlay.overlay);
}
}
onContextDrawerChanged: {
if (contextDrawer) {
contextDrawer.parent = Qt.binding(() => T.Overlay.overlay);
}
}
onPageStackChanged: {
if (pageStack) {
// contentItem is declared as CONSTANT, so binding isn't needed.
pageStack.parent = contentItem;
}
}
width: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 55
height: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 40
visible: true
Component.onCompleted: {
// Explicitly break the binding as we need this to be set only at startup.
// if the bindings are active, after this the window is resized by the
// compositor and then the bindings are reevaluated, then the window
// size would reset ignoring what the compositor asked.
// see BUG 433849
root.width = root.width;
root.height = root.height;
}
// This is needed because discover in mobile mode does not
// close with the global drawer open.
Shortcut {
sequence: root.quitAction.shortcut
enabled: root.quitAction.enabled
context: Qt.ApplicationShortcut
onActivated: root.close();
}
}
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import "templates" as T
import "private" as P
/**
* @brief AbstractCard is the base for cards.
*
* A Card is a visual object that serves as an entry point for more detailed information.
* An abstractCard is empty, providing just the look and the base properties and signals
* for an ItemDelegate. It can be filled with any custom layout of items,
* its content is organized in 3 properties: header, contentItem and footer.
* Use this only when you need particular custom contents, for a standard layout
* for cards, use the Card component.
*
* @see org::kde::kirigami::Card
* @inherit org::kde::kirigami::templates::AbstractCard
* @since 2.4
*/
T.AbstractCard {
id: root
background: P.DefaultCardBackground {
clickFeedback: root.showClickFeedback
hoverFeedback: root.hoverEnabled
}
}
@@ -0,0 +1,160 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQml
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kirigami.private as P
/**
* @brief An item that represents an abstract Action
* @inherit QtQuick.QQC2.Action
*/
QQC2.Action {
//BEGIN properties
/**
* @brief This property holds whether the graphic representation of the action
* is supposed to be visible.
*
* It's up to the action representation to honor this property.
*
* default: ``true``
*/
property bool visible: !fromQAction || fromQAction.visible
/**
* @brief This property holds the tooltip text that is shown when the cursor is hovering over the control.
*
* Leaving this undefined or setting it to an empty string means that no tooltip will be shown when
* the cursor is hovering over the control that triggers the tooltip.
* @warning Tooltips may not be supported on all platforms.
*/
property string tooltip
/**
* @brief This property sets whether this action is a separator action.
*
* default: ``false``
*/
property bool separator: false
/**
* @brief This property holds whether auto-exclusivity is enabled.
*
* If auto-exclusivity is enabled, checkable actions that belong to the
* same parent item behave as if they were part of the same ButtonGroup.
* Only one action can be checked at any time; checking another action
* automatically unchecks the previously checked one.
*
* default: ``false``
*/
property bool autoExclusive: false
/**
* @brief This property sets whether this action becomes a title displaying
* its child actions as sub-items in GlobalDrawers and ContextDrawers.
*
* default: ``false``
*
* @since 2.6
*/
property bool expandible: false
/**
* @brief This property holds the parent action.
*/
property T.Action parent
/**
* @brief This property sets this action's display type.
*
* These are provided to implementations to indicate a preference for certain display
* styles.
*
* default: ``Kirigami.DisplayHint.NoPreference``
*
* @note This property contains only preferences, implementations may choose to disregard them.
* @see org::kde::kirigami::DisplayHint
* @since 2.12
*/
property int displayHint: Kirigami.DisplayHint.NoPreference
/**
* @brief This property holds the component that should be used for displaying this action.
* @note This can be used to display custom components in the toolbar.
* @since 5.65
* @since 2.12
*/
property Component displayComponent
/**
* @brief This property holds a list of child actions.
*
* This is useful for tree-like menus, such as the GlobalDrawer.
*
* Example usage:
* @code
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
*
* Kirigami.Action {
* text: "Tools"
*
* QQC2.Action {
* text: "Action1"
* }
* Kirigami.Action {
* text: "Action2"
* }
* }
* @endcode
* @property list<T.Action> children
*/
default property list<T.Action> children
/**
* This property holds a QAction
*
* When provided Kirigami.Action will be initialized from the given QAction.
*
* @since Kirigami 6.4.0
*/
property QtObject fromQAction
//END properties
onChildrenChanged: {
children
.filter(action => action instanceof Kirigami.Action)
.forEach(action => {
action.parent = this;
});
}
/**
* @brief This property holds the action's visible child actions.
* @property list<T.Action> visibleChildren
*/
readonly property list<T.Action> visibleChildren: children
.filter(action => !(action instanceof Kirigami.Action) || action.visible)
shortcut: fromQAction?.shortcut
text: fromQAction?.text ?? ''
icon.name: fromQAction ? P.ActionHelper.iconName(fromQAction.icon) : ''
onTriggered: if (fromQAction) {
fromQAction.trigger();
}
checkable: fromQAction?.checkable ?? false
checked: fromQAction?.checked ?? false
enabled: !fromQAction || fromQAction.enabled
readonly property Shortcut alternateShortcut : Shortcut {
sequences: P.ActionHelper.alternateShortcuts(fromQAction)
onActivated: root.trigger()
}
}
@@ -0,0 +1,191 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* This is advanced textfield. It is recommended to use this class when there
* is a need to create a create a textfield with action buttons (e.g a clear
* action).
*
* Example usage for a search field:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ActionTextField {
* id: searchField
*
* placeholderText: i18n("Search…")
*
* focusSequence: StandardKey.Find
*
* rightActions: Kirigami.Action {
* icon.name: "edit-clear"
* visible: searchField.text.length > 0
* onTriggered: {
* searchField.clear();
* searchField.accepted();
* }
* }
*
* onAccepted: console.log("Search text is " + searchField.text);
* }
* @endcode
*
* @since 5.56
* @inherit QtQuick.Controls.TextField
*/
QQC2.TextField {
id: root
/**
* @brief This property holds a shortcut sequence that will focus the text field.
* @since 5.56
*/
property alias focusSequence: focusShortcut.sequence
/**
* @brief This property holds a list of actions that will be displayed on the left side of the text field.
*
* By default this list is empty.
*
* @since 5.56
*/
property list<T.Action> leftActions
/**
* @brief This property holds a list of actions that will be displayed on the right side of the text field.
*
* By default this list is empty.
*
* @since 5.56
*/
property list<T.Action> rightActions
property alias _leftActionsRow: leftActionsRow
property alias _rightActionsRow: rightActionsRow
hoverEnabled: true
// Manually setting this fixes alignment in RTL layouts
horizontalAlignment: TextInput.AlignLeft
leftPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? rightActionsRow : leftActionsRow).width
rightPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? leftActionsRow : rightActionsRow).width
Behavior on leftPadding {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on rightPadding {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Shortcut {
id: focusShortcut
enabled: root.visible && root.enabled
onActivated: {
root.forceActiveFocus(Qt.ShortcutFocusReason)
root.selectAll()
}
}
QQC2.ToolTip {
visible: focusShortcut.nativeText.length > 0 && root.text.length === 0 && root.hovered
text: focusShortcut.nativeText
}
component InlineActionIcon: Kirigami.Icon {
id: iconDelegate
required property T.Action modelData
implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
anchors.verticalCenter: parent.verticalCenter
source: modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source
visible: !(modelData instanceof Kirigami.Action) || modelData.visible
active: actionArea.containsPress || actionArea.activeFocus
enabled: modelData.enabled
MouseArea {
id: actionArea
anchors.fill: parent
activeFocusOnTab: true
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Accessible.name: iconDelegate.modelData.text
Accessible.role: Accessible.Button
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
clicked(null);
event.accepted = true;
break;
}
}
onClicked: mouse => iconDelegate.modelData.trigger()
}
QQC2.ToolTip {
visible: (actionArea.containsMouse || actionArea.activeFocus) && (iconDelegate.modelData.text.length > 0)
text: iconDelegate.modelData.text
}
}
Row {
id: leftActionsRow
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
layoutDirection: Qt.LeftToRight
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.left: parent.left
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.top: parent.top
anchors.topMargin: parent.topPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.bottomPadding
Repeater {
model: root.leftActions
InlineActionIcon { }
}
}
Row {
id: rightActionsRow
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
layoutDirection: Qt.RightToLeft
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.right: parent.right
anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.top: parent.top
anchors.topMargin: parent.topPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.bottomPadding
Repeater {
model: root.rightActions
InlineActionIcon { }
}
}
}
@@ -0,0 +1,243 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief A toolbar built out of a list of actions.
*
* The default representation for visible actions is a QtQuick.Controls.ToolButton, but
* it can be changed by setting the `Action.displayComponent` for an action.
* The default behavior of ActionToolBar is to display as many actions as possible,
* placing those that will not fit into an overflow menu. This can be changed by
* setting the `displayHint` property on an Action. For example, when setting the
* `DisplayHint.KeepVisible` display hint, ActionToolBar will try to keep that action
* in view as long as possible, using an icon-only button if a button with text
* does not fit.
*
* @inherit QtQuick.Controls.Control
* @since 2.5
*/
QQC2.Control {
id: root
//BEGIN properties
/**
* @brief This property holds a list of visible actions.
*
* The ActionToolBar will try to display as many actions as possible.
* Those that won't fit will go into an overflow menu.
*
* @property list<Action> actions
*/
readonly property alias actions: layout.actions
/**
* @brief This property holds whether the buttons will have a flat appearance.
*
* default: ``true``
*/
property bool flat: true
/**
* @brief This property determines how the icon and text are displayed within the button.
*
* Permitted values are:
* * ``Button.IconOnly``
* * ``Button.TextOnly``
* * ``Button.TextBesideIcon``
* * ``Button.TextUnderIcon``
*
* default: ``Controls.Button.TextBesideIcon``
*
* @see QtQuick.Controls.AbstractButton
* @property int display
*/
property int display: QQC2.Button.TextBesideIcon
/**
* @brief This property holds the alignment of the buttons.
*
* When there is more space available than required by the visible delegates,
* we need to determine how to place the delegates.
*
* When there is more space available than required by the visible action delegates,
* we need to determine where to position them.
*
* default: ``Qt.AlignLeft``
*
* @see Qt::AlignmentFlag
* @property int alignment
*/
property alias alignment: layout.alignment
/**
* @brief This property holds the position of the toolbar.
*
* If this ActionToolBar is the contentItem of a QQC2 Toolbar, the position is bound to the ToolBar's position
*
* Permitted values are:
* * ``ToolBar.Header``: The toolbar is at the top, as a window or page header.
* * ``ToolBar.Footer``: The toolbar is at the bottom, as a window or page footer.
*
* @property int position
*/
property int position: parent instanceof T.ToolBar ? parent.position : QQC2.ToolBar.Header
/**
* @brief This property holds the maximum width of the content of this ToolBar.
*
* If the toolbar's width is larger than this value, empty space will
* be added on the sides, according to the Alignment property.
*
* The value of this property is derived from the ToolBar's actions and their properties.
*
* @property int maximumContentWidth
*/
readonly property alias maximumContentWidth: layout.implicitWidth
/**
* @brief This property holds the name of the icon to use for the overflow menu button.
*
* default: ``"overflow-menu"``
*
* @since 5.65
* @since 2.12
*/
property string overflowIconName: "overflow-menu"
/**
* @brief This property holds the combined width of all visible delegates.
* @property int visibleWidth
*/
readonly property alias visibleWidth: layout.visibleWidth
/**
* @brief This property sets the handling method for items that do not match the toolbar's height.
*
* When toolbar items do not match the height of the toolbar, there are
* several ways we can deal with this. This property sets the preferred way.
*
* Permitted values are:
* * ``HeightMode.AlwaysCenter``
* * ``HeightMode.AlwaysFill``
* * ``AlwaysFill.ConstrainIfLarger``
*
* default: ``HeightMode::ConstrainIfLarger``
*
* @see ToolBarLayout::heightMode
* @see ToolBarLayout::HeightMode
* @property ToolBarLayout::HeightMode heightMode
*/
property alias heightMode: layout.heightMode
//END properties
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
Layout.minimumWidth: layout.minimumWidth
Layout.preferredWidth: 0
Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
Accessible.role: Accessible.ToolBar
contentItem: Kirigami.ToolBarLayout {
id: layout
spacing: Kirigami.Units.smallSpacing
layoutDirection: root.mirrored ? Qt.RightToLeft : Qt.LeftToRight
fullDelegate: P.PrivateActionToolButton {
flat: root.flat
display: root.display
action: Kirigami.ToolBarLayout.action
}
iconDelegate: P.PrivateActionToolButton {
flat: root.flat
display: QQC2.Button.IconOnly
action: Kirigami.ToolBarLayout.action
showMenuArrow: false
menuActions: {
if (action.displayComponent) {
return [action]
}
if (action instanceof Kirigami.Action) {
return action.children;
}
return []
}
}
separatorDelegate: QQC2.ToolSeparator {}
moreButton: P.PrivateActionToolButton {
flat: root.flat
action: Kirigami.Action {
tooltip: qsTr("More Actions")
icon.name: root.overflowIconName
displayHint: Kirigami.DisplayHint.IconOnly | Kirigami.DisplayHint.HideChildIndicator
}
Accessible.name: action.tooltip
menuActions: root.actions
menuComponent: P.ActionsMenu {
submenuComponent: P.ActionsMenu {
Binding {
target: parentItem
property: "visible"
value: layout.hiddenActions.includes(parentAction)
&& (!(parentAction instanceof Kirigami.Action) || parentAction.visible)
restoreMode: Binding.RestoreBinding
}
Binding {
target: parentItem
property: "autoExclusive"
value: action instanceof Kirigami.Action && action.autoExclusive
restoreMode: Binding.RestoreBinding
}
}
itemDelegate: P.ActionMenuItem {
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
autoExclusive: action instanceof Kirigami.Action && action.autoExclusive
}
loaderDelegate: Loader {
property T.Action action
height: visible ? implicitHeight : 0
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
}
separatorDelegate: QQC2.MenuSeparator {
property T.Action action
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
}
}
}
}
}
@@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief An item that provides the features of ApplicationWindow without the window itself.
*
* This allows embedding into a larger application.
* It's based around the PageRow component that allows adding/removing of pages.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationItem {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack.initialPage: Kirigami.Page {
* mainAction: Kirigami.Action {
* icon.name: "edit"
* onTriggered: {
* // do stuff
* }
* }
* contextualActions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* // ...
* }
* }
* @endcode
*/
Kirigami.AbstractApplicationItem {
id: root
/**
* @brief This property holds the PageRow used to allocate the pages and
* manage the transitions between them.
*
* It's using a PageRow, while having the same API as PageStack,
* it positions the pages as adjacent columns, with as many columns
* as can fit in the screen. An handheld device would usually have a single
* fullscreen column, a tablet device would have many tiled columns.
*
* @property org::kde::kirigami::PageRow pageStack
*/
readonly property alias pageStack: __pageStack
// Redefines here as here we can know a pointer to PageRow
wideScreen: width >= applicationWindow().pageStack.defaultColumnWidth * 2
Component.onCompleted: {
pageStack.currentItem?.forceActiveFocus();
}
Kirigami.PageRow {
id: __pageStack
anchors {
fill: parent
}
function goBack() {
// NOTE: drawers are handling the back button by themselves
const backEvent = {accepted: false}
if (root.pageStack.currentIndex >= 1) {
root.pageStack.currentItem.backRequested(backEvent);
if (!backEvent.accepted) {
root.pageStack.flickBack();
backEvent.accepted = true;
}
}
if (Kirigami.Settings.isMobile && !backEvent.accepted && Qt.platform.os !== "ios") {
Qt.quit();
}
}
function goForward() {
root.pageStack.currentIndex = Math.min(root.pageStack.depth - 1, root.pageStack.currentIndex + 1);
}
Keys.onBackPressed: event => {
goBack();
event.accepted = true;
}
Shortcut {
sequences: [StandardKey.Forward]
onActivated: __pageStack.goForward();
}
Shortcut {
sequences: [StandardKey.Back]
onActivated: __pageStack.goBack();
}
background: Rectangle {
color: root.color
}
focus: true
}
}
@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief A window that provides some basic features needed for all apps
*
* It's usually used as a root QML component for the application.
* It's based around the PageRow component, the application will be
* about pages adding and removal.
* For most of the usages, this class should be used instead
* of AbstractApplicationWindow
* @see AbstractApplicationWindow
*
* Setting a width and height property on the ApplicationWindow
* will set its initial size, but it won't set it as an automatically binding.
* to resize programmatically the ApplicationWindow they need to
* be assigned again in an imperative fashion
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack.initialPage: Kirigami.Page {
* mainAction: Kirigami.Action {
* icon.name: "edit"
* onTriggered: {
* // do stuff
* }
* }
* contextualActions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* [...]
* }
* [...]
* }
* @endcode
*
*/
Kirigami.AbstractApplicationWindow {
id: root
/**
* @brief This property holds the stack used to allocate the pages and to
* manage the transitions between them.
*
* It's using a PageRow, while having the same API as PageStack,
* it positions the pages as adjacent columns, with as many columns
* as can fit in the screen. An handheld device would usually have a single
* fullscreen column, a tablet device would have many tiled columns.
*
* @property org::kde::kirigami::PageRow pageStack
*/
readonly property alias pageStack: __pageStack
// Redefined here as here we can know a pointer to PageRow.
// We negate the canBeEnabled check because we don't want to factor in the automatic drawer provided by Kirigami for page actions for our calculations
wideScreen: width >= (root.pageStack.defaultColumnWidth) + ((contextDrawer && !(contextDrawer instanceof Kirigami.ContextDrawer)) ? contextDrawer.width : 0) + (globalDrawer ? globalDrawer.width : 0)
Component.onCompleted: {
pageStack.currentItem?.forceActiveFocus()
}
Kirigami.PageRow {
id: __pageStack
globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto
anchors {
fill: parent
}
focus: true
}
}
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief This is the standard layout of a Card.
*
* It is recommended to use this class when the concept of Cards is needed
* in the application.
*
* This Card has default items as header and footer. The header is an
* image that can contain an optional title and icon, accessible via the
* banner grouped property.
*
* The footer will show a series of toolbuttons (and eventual overflow menu)
* representing the actions list accessible with the list property actions.
* It is possible even tough is discouraged to override the footer:
* in this case the actions property shouldn't be used.
*
* @inherit org::kde::kirigami::AbstractCard
* @since 2.4
*/
Kirigami.AbstractCard {
id: root
/**
* @brief This property holds the clickable actions that will be available in the footer
* of the card.
*
* The actions will be represented by a list of ToolButtons with an optional overflow
* menu, when not all of them will fit in the available Card width.
*
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief This grouped property controls the banner image present in the header.
*
* This grouped property has the following sub-properties:
* * ``source: url``: The source for the image. It understands any URL valid for an Image component.
* * ``titleIcon: string``: The optional icon to put in the banner, either a freedesktop-compatible
* icon name (recommended) or any URL supported by QtQuick.Image.
* * ``title: string``: The title for the banner, shown as contrasting text over the image.
* * ``titleAlignment: Qt::Alignment``: The alignment of the title inside the image.
* default: ``Qt.AlignTop | Qt.AlignLeft``
* * ``titleLevel: int``: The Kirigami.Heading level for the title, which controls the font size.
* default: ``1``, which is the largest size.
* * ``titleWrapMode: QtQuick.Text::wrapMode``: Whether the header text should be able to wrap.
* default: ``Text.NoWrap``
*
* It also has the full set of properties that QtQuick.Image has, such as sourceSize and fillMode.
*
* @see org::kde::kirigami::private::BannerImage
* @property Image banner
*/
readonly property alias banner: bannerImage
Accessible.name: banner.title
header: Kirigami.Padding {
topPadding: -root.topPadding + root.background.border.width
leftPadding: -root.leftPadding + root.background.border.width
rightPadding: -root.rightPadding + root.background.border.width
bottomPadding: root.contentItem ? 0 : -root.bottomPadding + root.background.border.width
contentItem: P.BannerImage {
id: bannerImage
implicitWidth: Layout.preferredWidth
implicitHeight: (source.toString().length > 0 && sourceSize.width > 0 && sourceSize.height > 0 ? width / (sourceSize.width / sourceSize.height) : Layout.minimumHeight) + parent.topPadding + parent.bottomPadding
readonly property real widthWithBorder: width + root.background.border.width * 2
readonly property real heightWithBorder: height + root.background.border.width * 2
readonly property real radiusFromBackground: root.background.radius - root.background.border.width
corners.topLeftRadius: radiusFromBackground
corners.topRightRadius: radiusFromBackground
corners.bottomLeftRadius: radiusFromBackground
corners.bottomRightRadius: heightWithBorder < root.height ? 0 : radiusFromBackground
checkable: root.checkable
checked: root.checkable && root.checked
onToggled: checked => {
root.checked = checked;
root.toggled(checked);
}
}
}
onHeaderChanged: {
if (!header) {
return;
}
header.anchors.topMargin = Qt.binding(() => -root.topPadding);
header.anchors.leftMargin = Qt.binding(() => -root.leftPadding);
header.anchors.rightMargin = Qt.binding(() => -root.rightPadding);
header.anchors.bottomMargin = Qt.binding(() => 0);
}
footer: Kirigami.ActionToolBar {
id: actionsToolBar
actions: root.actions
position: QQC2.ToolBar.Footer
}
}
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A GridLayout optimized for showing one or two columns of cards,
* depending on the available space.
*
* It Should be used when the cards are not instantiated by a model or by a
* model which has always very few items.
*
* They are presented as a grid of two columns which will remain
* centered if the application is really wide, or become a single
* column if there is not enough space for two columns,
* such as a mobile phone screen.
*
* A CardsLayout should always be contained within a ColumnLayout.
*
* @since 2.4
* @inherit QtQuick.Layouts.GridLayout
*/
GridLayout {
/**
* @brief This property holds the maximum number of columns.
*
* This layout will never lay out the items in more columns than maximumColumns
*
* default: ``2``
*
* @since 2.5
*/
property int maximumColumns: 2
/**
* @brief This property holds the maximum width the columns may have.
*
* The cards will never become wider than this size; when the GridLayout is wider than
* maximumColumnWidth, it will switch from one to two columns.
*
* If the default needs to be overridden for some reason,
* it is advised to express this unit as a multiple
* of Kirigami.Units.gridUnit.
*
* default: ``20 * Kirigami.Units.gridUnit``
*/
property int maximumColumnWidth: Kirigami.Units.gridUnit * 20
/**
* @brief This property holds the minimum width the columns may have.
*
* The layout will try to dispose of items
* in a number of columns that will respect this size constraint.
*
* default: ``12 * Kirigami.Units.gridUnit``
*
* @since 2.5
*/
property int minimumColumnWidth: Kirigami.Units.gridUnit * 12
columns: Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
Math.floor(width/minimumColumnWidth),
Math.ceil(width/maximumColumnWidth)));
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
// NOTE: this default width which defaults to 2 columns is just to remove a binding loop on columns
width: maximumColumnWidth*2 + Kirigami.Units.largeSpacing
// same computation of columns, but on the parent size
Layout.preferredWidth: maximumColumnWidth * Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
Math.floor(parent.width/minimumColumnWidth),
Math.ceil(parent.width/maximumColumnWidth))) + Kirigami.Units.largeSpacing * (columns - 1)
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignHCenter
Component.onCompleted: childrenChanged()
onChildrenChanged: {
for (const child of children) {
child.Layout.fillHeight = true;
}
}
}
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* CardsListView is a ListView which can have AbstractCard as its delegate: it will
* automatically assign the proper spacings and margins around the cards adhering
* to the design guidelines.
*
* CardsListView should be used only with cards which can look good at any
* horizontal size, so it is recommended to directly use AbstractCard with an
* appropriate layout inside, because they are stretching for the whole list width.
*
* Therefore, it is discouraged to use it with the Card type.
*
* The choice between using this view with AbstractCard or a normal ListView
* is purely a choice based on aesthetics alone.
*
* It is recommended to use default values.
*
* @inherit QtQuick.ListView
* @since 2.4
*/
ListView {
id: root
spacing: Kirigami.Units.largeSpacing * 2
topMargin: headerPositioning !== ListView.InlineHeader ? spacing : 0
rightMargin: Kirigami.Units.largeSpacing * 2
leftMargin: Kirigami.Units.largeSpacing * 2
reuseItems: true
headerPositioning: ListView.OverlayHeader
Keys.onPressed: event => {
if (event.key === Qt.Key_Home) {
positionViewAtBeginning();
currentIndex = 0;
event.accepted = true;
}
else if (event.key === Qt.Key_End) {
positionViewAtEnd();
currentIndex = count - 1;
event.accepted = true;
}
}
}
@@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import "templates" as KT
import "private" as P
/**
* @brief A compact element that represents an attribute, action, or filter.
*
* Should be used in a group of multiple elements. e.g when displaying tags in a image viewer.
*
* Example usage:
* * @code
* import org.kde.kirigami as Kirigami
*
* Flow {
* Repeater {
* model: chipsModel
*
* Kirigami.Chip {
* text: model.text
* icon.name: "tag-symbolic"
* closable: model.closable
* onClicked: {
* [...]
* }
* onRemoved: {
* [...]
* }
* }
* }
* }
* @endcode
*
* @since 2.19
*/
KT.Chip {
id: chip
implicitWidth: layout.implicitWidth
implicitHeight: toolButton.implicitHeight
checkable: !closable
hoverEnabled: true
/**
* @brief This property holds the label item; used for accessing the usual Text properties.
* @property QtQuick.Controls.Label labelItem
*/
property alias labelItem: label
contentItem: RowLayout {
id: layout
spacing: 0
Kirigami.Icon {
id: icon
visible: icon.valid
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.leftMargin: Kirigami.Units.smallSpacing
color: chip.icon.color
isMask: chip.iconMask
source: chip.icon.name || chip.icon.source
}
QQC2.Label {
id: label
Layout.fillWidth: true
Layout.minimumWidth: Kirigami.Units.gridUnit * 1.5
Layout.leftMargin: icon.visible ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
Layout.rightMargin: chip.closable ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: chip.text
color: Kirigami.Theme.textColor
elide: Text.ElideRight
}
QQC2.ToolButton {
id: toolButton
visible: chip.closable
text: qsTr("Remove Tag")
icon.name: "edit-delete-remove"
icon.width: Kirigami.Units.iconSizes.sizeForLabels
icon.height: Kirigami.Units.iconSizes.sizeForLabels
display: QQC2.AbstractButton.IconOnly
onClicked: chip.removed()
}
}
background: P.DefaultChipBackground {}
}
@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as KP
/**
* A specialized type of drawer that will show a list of actions
* relevant to the application's current page.
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @inherit OverlayDrawer
*/
Kirigami.OverlayDrawer {
id: root
handleClosedIcon.source: null
handleOpenIcon.source: null
/**
* @brief A title for the action list that will be shown to the user when opens the drawer
*
* default: ``qsTr("Actions")``
*/
property string title: qsTr("Actions")
/**
* List of contextual actions to be displayed in a ListView.
*
* @see QtQuick.Action
* @see org::kde::kirigami::Action
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief Arbitrary content to show above the list view.
*
* default: `an Item containing a Kirigami.Heading that displays a title whose text is
* controlled by the title property.`
*
* @property Component header
* @since 2.7
*/
property alias header: menu.header
/**
* @brief Arbitrary content to show below the list view.
* @property Component footer
* @since 2.7
*/
property alias footer: menu.footer
// Not stored in a property, so we don't have to waste memory on an extra list.
function visibleActions() {
return actions.filter(
action => !(action instanceof Kirigami.Action) || action.visible
);
}
// Disable for empty menus or when we have a global toolbar
enabled: {
const pageStack = typeof applicationWindow !== "undefined" ? applicationWindow().pageStack : null;
const itemExistsButStyleIsNotToolBar = item => item && item.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar;
return menu.count > 0
&& (!pageStack
|| !pageStack.globalToolBar
|| (pageStack.layers.depth > 1
&& itemExistsButStyleIsNotToolBar(pageStack.layers.currentItem))
|| itemExistsButStyleIsNotToolBar(pageStack.trailingVisibleItem));
}
edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
drawerOpen: false
// list items go to edges, have their own padding
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
property bool handleVisible: {
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w) {
return w.controlsVisible;
}
}
// For a ContextDrawer its handle is hidden by default
return false;
}
contentItem: QQC2.ScrollView {
// this just to create the attached property
Kirigami.Theme.inherit: true
implicitWidth: Kirigami.Units.gridUnit * 20
ListView {
id: menu
interactive: contentHeight > height
model: root.visibleActions()
topMargin: root.handle.y > 0 ? menu.height - menu.contentHeight : 0
header: QQC2.ToolBar {
height: pageStack.globalToolBar.preferredHeight
width: parent.width
Kirigami.Heading {
id: heading
elide: Text.ElideRight
text: root.title
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: Kirigami.Units.largeSpacing
rightMargin: Kirigami.Units.largeSpacing
}
}
}
delegate: Column {
id: delegate
required property T.Action modelData
width: parent.width
KP.ContextDrawerActionItem {
tAction: delegate.modelData
width: parent.width
}
Repeater {
model: delegate.modelData instanceof Kirigami.Action && delegate.modelData.expandible
? delegate.modelData.children : null
delegate: KP.ContextDrawerActionItem {
width: parent.width
leftPadding: Kirigami.Units.gridUnit
opacity: !root.collapsed
}
}
}
}
}
}
@@ -0,0 +1,92 @@
/*
SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
SPDX-FileCopyrightText: 2024 Nate Graham <nate@kde.org>
SPDX-FileCopyrightText: 2024 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief An inline help button that shows a tooltip when clicked.
*
* Use this component when you want to explain details or usage of a feature of
* the UI, but the explanation is too long to fit in an inline label, and too
* important to put in a hover tooltip and risk the user missing it.
*
* @image html ContextualHelpButton.png "Example of ContextualHelpButton usage"
*
* Example usage:
* @code{.qml}
* import QtQuick.Controls as QQC2
* import QtQuick.Layouts
* import org.kde.kirigami as Kirigami
*
* RowLayout {
* spacing: Kirigami.Units.smallSpacing
*
* QQC2.CheckBox {
* text: i18n("Allow screen tearing in fullscreen windows")
* }
*
* Kirigami.ContextualHelpButton {
* toolTipText: i18n("With most displays, screen tearing reduces latency at the cost of some visual fidelity at high framerates. Note that not all graphics drivers support this setting.")
* }
* }
*
* @endcode
*/
QQC2.ToolButton {
id: root
property alias toolTipText: toolTip.text
property bool toolTipVisible: false
text: qsTr("Show Contextual Help")
icon.name: "help-contextual-symbolic"
display: QQC2.ToolButton.IconOnly
Accessible.description: toolTipText
onReleased: {
toolTip.delay = toolTipVisible ? Kirigami.Units.toolTipDelay : 0;
toolTipVisible = !toolTipVisible;
}
onActiveFocusChanged: {
toolTip.delay = Kirigami.Units.toolTipDelay;
toolTipVisible = false;
}
Layout.maximumHeight: parent?.height ?? -1
QQC2.ToolTip {
id: toolTip
clip: true
visible: root.hovered || root.toolTipVisible || toolTipHandler.hovered
onVisibleChanged: {
if (!visible && root.toolTipVisible) {
root.toolTipVisible = false;
delay = Kirigami.Units.toolTipDelay;
}
}
timeout: -1 // Don't disappear while the user might still be reading it!
HoverHandler {
// Also keep the tooltip open while hovering it
// Fixes the flickering when the popup covers the button
id: toolTipHandler
enabled: !root.toolTipVisible // Only if activated by hovering
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.WhatsThisCursor
acceptedButtons: Qt.NoButton
}
}
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
//TODO KF6: how much is this used? can be removed?
/**
* @brief FlexColumn is a column that grows in width to a fixed cap.
* @inherit QtQuick.Layouts.ColumnLayout
*/
ColumnLayout {
id: __outer
default property alias columnChildren: __inner.children
/**
* @brief This property holds the column's offset from the cross axis.
*
* Note that padding is applied on both sides
* when the column is aligned to a centered cross axis.
*
* default: ``Kirigami.Units.largeSpacing``
*/
property real padding: Kirigami.Units.largeSpacing
/**
* @brief This property holds maximum column width.
*
* default: ``Kirigami.Units.gridUnit * 50``
*/
property real maximumWidth: Kirigami.Units.gridUnit * 50
/**
* @brief This property sets column's alignment when it hits its maximum width.
*
* default: ``Qt.AlignHCenter | Qt.AlignTop``
*
* @property Qt::Alignment alignment
*/
property int alignment: Qt.AlignHCenter | Qt.AlignTop
/**
* @brief This property holds the inner column's width.
*/
property real innerWidth: __inner.width
Layout.fillWidth: true
Layout.fillHeight: true
enum CrossAxis {
Left,
Center,
Right
}
ColumnLayout {
id: __inner
spacing: __outer.spacing
Layout.maximumWidth: __outer.maximumWidth
Layout.leftMargin: __outer.alignment & Qt.AlignLeft || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
Layout.rightMargin: __outer.alignment & Qt.AlignRight || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
Layout.alignment: __outer.alignment
}
}
@@ -0,0 +1,627 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as KP
/**
* A specialized form of the Drawer intended for showing an application's
* always-available global actions. Think of it like a mobile version of
* a desktop application's menubar.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
* }
* @endcode
*/
Kirigami.OverlayDrawer {
id: root
edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge
handleClosedIcon.source: null
handleOpenIcon.source: null
handleVisible: {
// When drawer is inline with content and opened, there is no point is showing handle.
if (!modal && drawerOpen) {
return false;
}
// GlobalDrawer can be hidden by controlsVisible...
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w && !w.controlsVisible) {
return false;
}
}
// ...but it still performs additional checks.
return !isMenu || Kirigami.Settings.isMobile;
}
enabled: !isMenu || Kirigami.Settings.isMobile
//BEGIN properties
/**
* @brief This property holds the title displayed at the top of the drawer.
* @see org::kde::kirigami::private::BannerImage::title
* @property string title
*/
property string title
/**
* @brief This property holds an icon to be displayed alongside the title.
* @see org::kde::kirigami::private::BannerImage::titleIcon
* @see org::kde::kirigami::Icon::source
* @property var titleIcon
*/
property var titleIcon
/**
* @brief This property holds the actions displayed in the drawer.
*
* 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 as Kirigami
*
* Kirigami.ApplicationWindow {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
* }
* @endcode
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief This property holds an item that will always be displayed at the top of the drawer.
*
* If the drawer contents can be scrolled, this item will stay still and won't scroll.
*
* @note This property is mainly intended for toolbars.
* @since 2.12
*/
property alias header: mainLayout.header
/**
* @brief This property holds an item that will always be displayed at the bottom of the drawer.
*
* If the drawer contents can be scrolled, this item will stay still and won't scroll.
*
* @note This property is mainly intended for toolbars.
* @since 6.0
*/
property alias footer: mainLayout.footer
/**
* @brief This property holds items that are displayed above the actions.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [...]
* topContent: [Button {
* text: "Button"
* onClicked: //do stuff
* }]
* }
* [...]
* }
* @endcode
* @property list<QtObject> topContent
*/
property alias topContent: topContent.data
/**
* @brief This property holds items that are displayed under the actions.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [...]
* Button {
* text: "Button"
* onClicked: //do stuff
* }
* }
* [...]
* }
* @endcode
* @note This is a `default` property.
* @property list<QtObject> content
*/
default property alias content: mainContent.data
/**
* @brief This property sets whether content items at the top should be shown.
* when the drawer is collapsed as a sidebar.
*
* If you want to keep some items visible and some invisible, set this to
* false and control the visibility/opacity of individual items,
* binded to the collapsed property
*
* default: ``false``
*
* @since 2.5
*/
property bool showTopContentWhenCollapsed: false
/**
* @brief This property sets whether content items at the bottom should be shown.
* when the drawer is collapsed as a sidebar.
*
* If you want to keep some items visible and some invisible, set this to
* false and control the visibility/opacity of individual items,
* binded to the collapsed property
*
* default: ``false``
*
* @see content
* @since 2.5
*/
property bool showContentWhenCollapsed: false
// TODO
property bool showHeaderWhenCollapsed: false
/**
* @brief This property sets whether activating a leaf action resets the
* menu to show leaf's parent actions.
*
* A leaf action is an action without any child actions.
*
* default: ``true``
*/
property bool resetMenuOnTriggered: true
/**
* @brief This property points to the action acting as a submenu
*/
readonly property T.Action currentSubMenu: stackView.currentItem?.current ?? null
/**
* @brief This property sets whether the drawer becomes a menu on the desktop.
*
* default: ``false``
*
* @since 2.11
*/
property bool isMenu: false
/**
* @brief This property sets the visibility of the collapse button
* when the drawer collapsible.
*
* default: ``true``
*
* @since 2.12
*/
property bool collapseButtonVisible: true
//END properties
/**
* @brief This function reverts the menu back to its initial state
*/
function resetMenu() {
stackView.pop(stackView.get(0, T.StackView.DontLoad));
if (root.modal) {
root.drawerOpen = false;
}
}
// rightPadding: !Kirigami.Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Kirigami.Units.gridUnit : Kirigami.Units.smallSpacing
Kirigami.Theme.colorSet: modal ? Kirigami.Theme.Window : Kirigami.Theme.View
onIsMenuChanged: drawerOpen = false
Component {
id: menuComponent
Column {
property alias model: actionsRepeater.model
property T.Action current
property int level: 0
spacing: 0
Layout.maximumHeight: Layout.minimumHeight
QQC2.ItemDelegate {
id: backItem
visible: level > 0
width: parent.width
icon.name: mirrored ? "go-previous-symbolic-rtl" : "go-previous-symbolic"
text: Kirigami.MnemonicData.richTextLabel
activeFocusOnTab: true
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: qsTr("Back")
onClicked: stackView.pop()
Keys.onEnterPressed: stackView.pop()
Keys.onReturnPressed: stackView.pop()
Keys.onDownPressed: nextItemInFocusChain().focus = true
Keys.onUpPressed: nextItemInFocusChain(false).focus = true
}
Shortcut {
sequence: backItem.Kirigami.MnemonicData.sequence
onActivated: backItem.clicked()
}
Repeater {
id: actionsRepeater
readonly property bool withSections: {
for (const action of root.actions) {
if (action.hasOwnProperty("expandible") && action.expandible) {
return true;
}
}
return false;
}
model: root.actions
delegate: ActionDelegate {
required property T.Action modelData
tAction: modelData
withSections: actionsRepeater.withSections
}
}
}
}
component ActionDelegate : Column {
id: delegate
required property int index
required property T.Action tAction
required property bool withSections
// `as` case operator is still buggy
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool isExpanded: {
return !root.collapsed
&& kAction
&& kAction.expandible
&& kAction.children.length > 0;
}
visible: kAction?.visible ?? true
width: parent.width
KP.GlobalDrawerActionItem {
Kirigami.Theme.colorSet: !root.modal && !root.collapsed && delegate.withSections
? Kirigami.Theme.Window : parent.Kirigami.Theme.colorSet
visible: !delegate.isExpanded
width: parent.width
tAction: delegate.tAction
onCheckedChanged: {
// move every checked item into view
if (checked && topContent.height + backItem.height + (delegate.index + 1) * height - mainFlickable.contentY > mainFlickable.height) {
mainFlickable.contentY += height
}
}
}
Item {
id: headerItem
visible: delegate.isExpanded
height: sectionHeader.implicitHeight
width: parent.width
Kirigami.ListSectionHeader {
id: sectionHeader
anchors.fill: parent
Kirigami.Theme.colorSet: root.modal ? Kirigami.Theme.View : Kirigami.Theme.Window
contentItem: RowLayout {
spacing: sectionHeader.spacing
Kirigami.Icon {
property int size: Kirigami.Units.iconSizes.smallMedium
Layout.minimumHeight: size
Layout.maximumHeight: size
Layout.minimumWidth: size
Layout.maximumWidth: size
source: delegate.tAction.icon.name || delegate.tAction.icon.source
}
Kirigami.Heading {
level: 4
text: delegate.tAction.text
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
Repeater {
model: delegate.isExpanded ? (delegate.kAction?.children ?? null) : null
NestedActionDelegate {
required property T.Action modelData
tAction: modelData
withSections: delegate.withSections
}
}
}
component NestedActionDelegate : KP.GlobalDrawerActionItem {
required property bool withSections
width: parent.width
opacity: !root.collapsed
leftPadding: withSections && !root.collapsed && !root.modal ? padding * 2 : padding * 4
}
contentItem: Kirigami.HeaderFooterLayout {
id: mainLayout
anchors {
fill: parent
topMargin: root.collapsed && !showHeaderWhenCollapsed ? -contentItem.y : 0
}
Behavior on anchors.topMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
header: RowLayout {
visible: root.title.length > 0 || Boolean(root.titleIcon)
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: root.titleIcon
}
Kirigami.Heading {
text: root.title
elide: Text.ElideRight
visible: !root.collapsed
Layout.fillWidth: true
}
}
contentItem: QQC2.ScrollView {
id: scrollView
//ensure the attached property exists
Kirigami.Theme.inherit: true
// HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, root.parent.width * 0.8)
Flickable {
id: mainFlickable
contentWidth: width
contentHeight: mainColumn.Layout.minimumHeight
clip: (mainLayout.header?.visible ?? false) || (mainLayout.footer?.visible ?? false)
ColumnLayout {
id: mainColumn
width: mainFlickable.width
spacing: 0
height: Math.max(scrollView.height, Layout.minimumHeight)
ColumnLayout {
id: topContent
spacing: 0
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: root.leftPadding
Layout.rightMargin: root.rightPadding
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.topMargin: root.topPadding
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: implicitHeight * opacity
// 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 && opacity > 0
opacity: !root.collapsed || showTopContentWhenCollapsed
Behavior on opacity {
// not an animator as is binded
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
T.StackView {
id: stackView
property KP.ActionsMenu openSubMenu
clip: true
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: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
popExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
pushEnter: Transition {
NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
pushExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
replaceEnter: Transition {
NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
replaceExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: root.actions.length > 0
Layout.minimumHeight: Kirigami.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 && (opacity > 0 || mainContentAnimator.running)
opacity: !root.collapsed || showContentWhenCollapsed
Behavior on opacity {
OpacityAnimator {
id: mainContentAnimator
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Item {
Layout.minimumWidth: Kirigami.Units.smallSpacing
Layout.minimumHeight: root.bottomPadding
}
QQC2.ToolButton {
Layout.fillWidth: true
icon.name: {
if (root.collapsible && root.collapseButtonVisible) {
// Check for edge regardless of RTL/locale/mirrored status,
// because edge can be set externally.
const mirrored = root.edge === Qt.RightEdge;
if (root.collapsed) {
return mirrored ? "sidebar-expand-right" : "sidebar-expand-left";
} else {
return mirrored ? "sidebar-collapse-right" : "sidebar-collapse-left";
}
}
return "";
}
visible: root.collapsible && root.collapseButtonVisible
text: root.collapsed ? "" : qsTr("Close Sidebar")
onClicked: root.collapsed = !root.collapsed
QQC2.ToolTip.visible: root.collapsed && (Kirigami.Settings.tabletMode ? pressed : hovered)
QQC2.ToolTip.text: qsTr("Open Sidebar")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
}
@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2012 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* @brief A heading label used for subsections of texts.
*
* The characteristics of the text will be automatically set according to the
* Kirigami.Theme. Use this components for section titles or headings in your UI,
* for example page or section titles.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
* [...]
* Column {
* Kirigami.Heading {
* text: "Apples in the sunlight"
* level: 2
* }
* [...]
* }
* @endcode
*
* The most important property is "text", which applies to the text property of
* Label. See the Label component from QtQuick.Controls 2 and primitive QML Text
* element API for additional properties, methods and signals.
*
* @inherit QtQuick.Controls.Label
*/
QQC2.Label {
id: heading
/**
* @brief This property holds the level of the heading, which determines its size.
*
* This property holds the level, which determines how large the header is.
*
* Acceptable values range from 1 (big) to 5 (small).
*
* default: ``1``
*/
property int level: 1
/**
* @brief This enumeration defines heading types.
*
* This enum helps with heading visibility (making it less or more important).
*/
enum Type {
Normal,
Primary,
Secondary
}
/**
* @brief This property holds the heading type.
*
* The type of the heading. This can be:
* * ``Kirigami.Heading.Type.Normal``: Create a normal heading (default)
* * ``Kirigami.Heading.Type.Primary``: Makes the heading more prominent. Useful
* when making the heading bigger is not enough.
* * ``Kirigami.Heading.Type.Secondary``: Makes the heading less prominent.
* Useful when an heading is for a less important section in an application.
*
* @property Heading::Type type
* @since 5.82
*/
property int type: Heading.Type.Normal
font.pointSize: {
let factor = 1;
switch (heading.level) {
case 1:
factor = 1.35;
break;
case 2:
factor = 1.20;
break;
case 3:
factor = 1.15;
break;
case 4:
factor = 1.10;
break;
default:
break;
}
return Kirigami.Theme.defaultFont.pointSize * factor;
}
font.weight: type === Heading.Type.Primary ? Font.DemiBold : Font.Normal
opacity: type === Heading.Type.Secondary ? 0.7 : 1
Accessible.role: Accessible.Heading
}
@@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates as KT
/**
* An inline message item with support for informational, positive,
* warning and error types, and with support for associated actions.
*
* InlineMessage can be used to give information to the user or
* interact with the user, without requiring the use of a dialog.
*
* The InlineMessage item is hidden by default. It also manages its
* height (and implicitHeight) during an animated reveal when shown.
* You should avoid setting height on an InlineMessage unless it is
* already visible.
*
* Optionally an icon can be set, defaulting to an icon appropriate
* to the message type otherwise.
*
* Optionally a close button can be shown.
*
* Actions are added from left to right. If more actions are set than
* can fit, an overflow menu is provided.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.InlineMessage {
* type: Kirigami.MessageType.Error
*
* text: i18n("My error message")
*
* actions: [
* Kirigami.Action {
* icon.name: "list-add"
* text: i18n("Add")
* onTriggered: source => {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: i18n("Edit")
* onTriggered: source => {
* // do stuff
* }
* }
* ]
* }
* @endcode
* @inherit org::kde::kirigami::templates::InlineMessage
* @since 5.45
*/
KT.InlineMessage {
id: root
// a rectangle padded with anchors.margins is used to simulate a border
leftPadding: bgFillRect.anchors.leftMargin + Kirigami.Units.smallSpacing
topPadding: bgFillRect.anchors.topMargin + Kirigami.Units.smallSpacing
rightPadding: bgFillRect.anchors.rightMargin + Kirigami.Units.smallSpacing
bottomPadding: bgFillRect.anchors.bottomMargin + Kirigami.Units.smallSpacing
background: Rectangle {
id: bgBorderRect
color: switch (root.type) {
case Kirigami.MessageType.Positive: return Kirigami.Theme.positiveTextColor;
case Kirigami.MessageType.Warning: return Kirigami.Theme.neutralTextColor;
case Kirigami.MessageType.Error: return Kirigami.Theme.negativeTextColor;
default: return Kirigami.Theme.activeTextColor;
}
radius: root.position === KT.InlineMessage.Position.Inline ? Kirigami.Units.cornerRadius : 0
Rectangle {
id: bgFillRect
anchors.fill: parent
anchors {
leftMargin: root.position === KT.InlineMessage.Position.Inline ? 1 : 0
topMargin: root.position === KT.InlineMessage.Position.Header ? 0 : 1
rightMargin: root.position === KT.InlineMessage.Position.Inline ? 1 : 0
bottomMargin: root.position === KT.InlineMessage.Position.Footer ? 0 : 1
}
color: Kirigami.Theme.backgroundColor
radius: bgBorderRect.radius * 0.60
}
Rectangle {
anchors.fill: bgFillRect
color: bgBorderRect.color
opacity: 0.20
radius: bgFillRect.radius
}
}
}
@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2023 Nate Graham <nate@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief A fancy inline view header showing a title and optional actions.
*
* Designed to be set as the header: property of a ListView or GridView, this
* component provides a fancy inline header suitable for explaining the contents
* of its view to the user in an attractive and standardized way. Actions globally
* relevant to the view can be defined using the actions: property. They will
* appear on the right side of the header as buttons, and collapse into an
* overflow menu when there isn't room to show them all.
*
* The width: property must be manually set to the parent view's width.
*
* Example usage:
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
*
* headerPositioning: ListView.OverlayHeader
* header: InlineViewHeader {
* width: listView.width
* text: "My amazing view"
* actions: [
* Kirigami.Action {
* icon.name: "list-add-symbolic"
* text: "Add item"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
*
* model: [...]
* delegate: [...]
* }
* @endcode
* @inherit QtQuick.QQC2.ToolBar
*/
T.ToolBar {
id: root
//BEGIN properties
/**
* @brief This property holds the title text.
*/
property string text
/**
* This property holds the list of actions to show on the header. Actions
* are added from left to right. If more actions are set than can fit, an
* overflow menu is provided.
*/
property list<T.Action> actions
//END properties
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
Math.ceil(label.implicitWidth)
+ rowLayout.spacing
+ Math.ceil(Math.max(buttonsLoader.implicitWidth, buttonsLoader.Layout.minimumWidth))
+ leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
topPadding: Kirigami.Units.smallSpacing + (root.position === T.ToolBar.Footer ? separator.implicitHeight : 0)
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing + (root.position === T.ToolBar.Header ? separator.implicitHeight : 0)
z: 999 // don't let content overlap it
// HACK Due to the lack of a GridView.headerPositioning property,
// we need to "stick" ourselves to the top manually by translating Y accordingly.
// see see https://bugreports.qt.io/browse/QTBUG-117035.
// Conveniently, GridView is only attached to the root of the delegate (or headerItem),
// so this will only be done if the InlineViewHeader itself is the header item.
// And of course it won't be there for ListView either, where we have headerPositioning.
transform: Translate {
y: root.GridView.view ? root.GridView.view.contentY + root.height : 0
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
// We want a color that's basically halfway between the view background
// color and the window background color. But due to the use of color
// scopes, only one will be available at a time. So to get basically the
// same thing, we blend the view background color with a smidgen of the
// text color.
color: Qt.tint(Kirigami.Theme.backgroundColor, Qt.alpha(Kirigami.Theme.textColor, 0.03))
Kirigami.Separator {
id: separator
anchors {
top: root.position === T.ToolBar.Footer ? parent.top : undefined
left: parent.left
right: parent.right
bottom: root.position === T.ToolBar.Header ? parent.bottom : undefined
}
}
}
contentItem: RowLayout {
id: rowLayout
spacing: 0
Kirigami.Heading {
id: label
Layout.fillWidth: !buttonsLoader.active
Layout.maximumWidth: {
if (!buttonsLoader.active) {
return -1;
}
return rowLayout.width
- rowLayout.spacing
- buttonsLoader.Layout.minimumWidth;
}
Layout.alignment: Qt.AlignVCenter
level: 2
text: root.text
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
}
Loader {
id: buttonsLoader
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: item?.Layout.minimumWidth ?? 0
active: root.actions.length > 0
sourceComponent: Kirigami.ActionToolBar {
actions: root.actions
alignment: Qt.AlignRight
}
}
}
}
@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief A button that looks like a link.
*
* It uses the link color settings and triggers an action when clicked.
*
* Maps to the Command Link in the HIG:
* https://develop.kde.org/hig/components/navigation/commandlink/
*
* @since 5.52
* @since org.kde.kirigami 2.6
* @inherit QtQuick.Controls.Label
*/
QQC2.Label {
id: control
property T.Action action
/**
* @brief This property holds the mouse buttons that the mouse area reacts to.
* @see QtQuick.MouseArea::acceptedButtons
* @property Qt::MouseButtons acceptedButtons
*/
property alias acceptedButtons: area.acceptedButtons
/**
* @brief This property holds the mouse area element covering the button.
* @property MouseArea area
*/
property alias mouseArea: area
activeFocusOnTab: true
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.onPressAction: clicked({ button: Qt.LeftButton })
text: action?.text ?? ""
enabled: action?.enabled ?? true
onClicked: action?.trigger()
font.bold: activeFocus
font.underline: enabled
color: enabled ? Kirigami.Theme.linkColor : Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
signal pressed(var mouse)
signal clicked(var mouse)
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
control.clicked({ button: Qt.LeftButton });
event.accepted = true;
break;
case Qt.Key_Menu:
control.pressed({ button: Qt.RightButton });
event.accepted = true;
break;
}
}
MouseArea {
id: area
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: mouse => control.clicked(mouse)
onPressed: mouse => control.pressed(mouse)
}
}
@@ -0,0 +1,230 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* Implements a drag handle supposed to be in items in ListViews to reorder items
* The ListView must visualize a model which supports item reordering,
* such as ListModel.move() or QAbstractItemModel instances with moveRows() correctly implemented.
* In order for ListItemDragHandle to work correctly, the listItem that is being dragged
* should not directly be the delegate of the ListView, but a child of it.
*
* It is recommended to use DelagateRecycler as base delegate like the following code:
* @code
* import QtQuick
* import QtQuick.Layouts
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
* ...
* Component {
* id: delegateComponent
* QQC2.ItemDelegate {
* id: listItem
* contentItem: RowLayout {
* Kirigami.ListItemDragHandle {
* listItem: listItem
* listView: mainList
* onMoveRequested: (oldIndex, newIndex) => {
* listModel.move(oldIndex, newIndex, 1);
* }
* }
* QQC2.Label {
* text: model.label
* }
* }
* }
* }
* ListView {
* id: mainList
*
* model: ListModel {
* id: listModel
* ListElement {
* label: "Item 1"
* }
* ListElement {
* label: "Item 2"
* }
* ListElement {
* label: "Item 3"
* }
* }
* //this is optional to make list items animated when reordered
* moveDisplaced: Transition {
* YAnimator {
* duration: Kirigami.Units.longDuration
* easing.type: Easing.InOutQuad
* }
* }
* delegate: Loader {
* width: mainList.width
* sourceComponent: delegateComponent
* }
* }
* ...
* @endcode
*
* @since 2.5
* @inherit QtQuick.Item
*/
Item {
id: root
/**
* @brief This property holds the delegate that will be dragged around.
*
* This item *must* be a child of the actual ListView's delegate.
*/
property Item listItem
/**
* @brief This property holds the ListView that the delegate belong to.
*/
property ListView listView
/**
* @brief This signal is emitted when the drag handle wants to move the item in the model.
*
* The following example does the move in the case a ListModel is used:
* @code
* onMoveRequested: (oldIndex, newIndex) => {
* listModel.move(oldIndex, newIndex, 1);
* }
* @endcode
* @param oldIndex the index the item is currently at
* @param newIndex the index we want to move the item to
*/
signal moveRequested(int oldIndex, int newIndex)
/**
* @brief This signal is emitted when the drag operation is complete and the item has been
* dropped in the new final position.
*/
signal dropped()
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
MouseArea {
id: mouseArea
anchors.fill: parent
drag {
target: listItem
axis: Drag.YAxis
minimumY: 0
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
preventStealing: true
Kirigami.Icon {
id: internal
anchors.fill: parent
source: "handle-sort"
opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
property int startY
property int mouseDownY
property Item originalParent
property int listItemLastY
property bool draggingUp
function arrangeItem() {
const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(mouseArea, 0, internal.mouseDownY).y);
if (newIndex > -1 && ((internal.draggingUp && newIndex < index) || (!internal.draggingUp && newIndex > index))) {
root.moveRequested(index, newIndex);
}
}
}
onPressed: mouse => {
internal.originalParent = listItem.parent;
listItem.parent = listView;
listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
internal.originalParent.z = 99;
internal.startY = listItem.y;
internal.listItemLastY = listItem.y;
internal.mouseDownY = mouse.y;
// while dragging listItem's height could change
// we want a const maximumY during the dragging time
mouseArea.drag.maximumY = listView.height - listItem.height;
}
onPositionChanged: mouse => {
if (!pressed || listItem.y === internal.listItemLastY) {
return;
}
internal.draggingUp = listItem.y < internal.listItemLastY
internal.listItemLastY = listItem.y;
internal.arrangeItem();
// autoscroll when the dragging item reaches the listView's top/bottom boundary
scrollTimer.running = (listView.contentHeight > listView.height)
&& ((listItem.y === 0 && !listView.atYBeginning)
|| (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd));
}
onReleased: mouse => dropped()
onCanceled: dropped()
function dropped() {
listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
listItem.parent = internal.originalParent;
dropAnimation.running = true;
scrollTimer.running = false;
root.dropped();
}
SequentialAnimation {
id: dropAnimation
YAnimator {
target: listItem
from: listItem.y
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
PropertyAction {
target: listItem.parent
property: "z"
value: 0
}
}
Timer {
id: scrollTimer
interval: 50
repeat: true
onTriggered: {
if (internal.draggingUp) {
listView.contentY -= Kirigami.Units.gridUnit;
if (listView.atYBeginning) {
listView.positionViewAtBeginning();
stop();
}
} else {
listView.contentY += Kirigami.Units.gridUnit;
if (listView.atYEnd) {
listView.positionViewAtEnd();
stop();
}
}
internal.arrangeItem();
}
}
}
}
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2019 Björn Feber <bfeber@protonmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A section delegate for the primitive ListView component.
*
* It's intended to make all listviews look coherent.
*
* Any additional content items will be positioned in a row at the trailing side
* of this component.
*
* Example usage:
* @code
* import QtQuick
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
*
* ListView {
* section.delegate: Kirigami.ListSectionHeader {
* text: section
*
* QQC2.Button {
* text: "Button 1"
* }
* QQC2.Button {
* text: "Button 2"
* }
* }
* }
* @endcode
*/
QQC2.ItemDelegate {
id: listSection
/**
* @brief This property sets the text of the ListView's section header.
* @property string label
* @deprecated since 6.2 Use base type's AbstractButton::text property directly
*/
@Deprecated { reason: "Use base type's AbstractButton::text property directly" }
property alias label: listSection.text
default property alias _contents: rowLayout.data
hoverEnabled: false
activeFocusOnTab: false
// we do not need a background
background: Item {}
topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
Accessible.role: Accessible.Heading
contentItem: RowLayout {
id: rowLayout
spacing: Kirigami.Units.largeSpacing
Kirigami.Heading {
Layout.maximumWidth: rowLayout.width
Layout.alignment: Qt.AlignVCenter
opacity: 0.7
level: 5
type: Kirigami.Heading.Primary
text: listSection.text
elide: Text.ElideRight
// we override the Primary type's font weight (DemiBold) for Bold for contrast with small text
font.weight: Font.Bold
Accessible.ignored: true
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Accessible.ignored: true
}
}
}
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A placeholder for loading pages.
*
* Example usage:
* @code{.qml}
* Kirigami.Page {
* Kirigami.LoadingPlaceholder {
* anchors.centerIn: parent
* }
* }
* @endcode
* @code{.qml}
* Kirigami.Page {
* Kirigami.LoadingPlaceholder {
* anchors.centerIn: parent
* determinate: true
* progressBar.value: loadingValue
* }
* }
* @endcode
* @inherit org::kde::kirigami::PlaceholderMessage
*/
Kirigami.PlaceholderMessage {
id: loadingPlaceholder
/**
* @brief This property holds whether the loading message shows a
* determinate progress bar or not.
*
* This should be true if you want to display the actual
* percentage when it's loading.
*
* default: ``false``
*/
property bool determinate: false
/**
* @brief This property holds a progress bar.
*
* This should be used to access the progress bar to change its value.
*
* @property QtQuick.Controls.ProgressBar _progressBar
*/
property alias progressBar: _progressBar
text: qsTr("Loading…")
QQC2.ProgressBar {
id: _progressBar
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 20
indeterminate: !determinate
from: 0
to: 100
}
}
@@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQml
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief Page navigation tab-bar, used as an alternative to sidebars for 3-5 elements.
*
* Can be combined with secondary toolbars above (if in the footer) to provide page actions.
*
* Example usage:
* @code{.qml}
* import QtQuick
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* title: "Clock"
*
* pageStack.initialPage: worldPage
*
* Kirigami.Page {
* id: worldPage
* title: "World"
* visible: false
* }
* Kirigami.Page {
* id: timersPage
* title: "Timers"
* visible: false
* }
* Kirigami.Page {
* id: stopwatchPage
* title: "Stopwatch"
* visible: false
* }
* Kirigami.Page {
* id: alarmsPage
* title: "Alarms"
* visible: false
* }
*
* footer: Kirigami.NavigationTabBar {
* actions: [
* Kirigami.Action {
* icon.name: "globe"
* text: "World"
* checked: worldPage.visible
* onTriggered: {
* if (!worldPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(worldPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "player-time"
* text: "Timers"
* checked: timersPage.visible
* onTriggered: {
* if (!timersPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(timersPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "chronometer"
* text: "Stopwatch"
* checked: stopwatchPage.visible
* onTriggered: {
* if (!stopwatchPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(stopwatchPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "notifications"
* text: "Alarms"
* checked: alarmsPage.visible
* onTriggered: {
* if (!alarmsPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(alarmsPage);
* }
* }
* }
* ]
* }
* }
* @endcode
*
* @see NavigationTabButton
* @since 5.87
* @since org.kde.kirigami 2.19
* @inherit QtQuick.Templates.Toolbar
*/
QQC2.ToolBar {
id: root
//BEGIN properties
/**
* @brief This property holds the list of actions to be displayed in the toolbar.
*/
property list<T.Action> actions
/**
* @brief This property holds a subset of visible actions of the list of actions.
*
* An action is considered visible if it is either a Kirigami.Action with
* ``visible`` property set to true, or it is a plain QQC2.Action.
*/
readonly property list<T.Action> visibleActions: actions
// Note: instanceof check implies `!== null`
.filter(action => action instanceof Kirigami.Action
? action.visible
: action !== null
)
/**
* @brief The property holds the maximum width of the toolbar actions, before margins are added.
*/
property real maximumContentWidth: {
const minDelegateWidth = Kirigami.Units.gridUnit * 5;
// Always have at least the width of 5 items, so that small amounts of actions look natural.
return minDelegateWidth * Math.max(visibleActions.length, 5);
}
/**
* @brief This property holds the index of currently checked tab.
*
* If the index set is out of bounds, or the triggered signal did not change any checked property of an action, the index
* will remain the same.
*/
property int currentIndex: tabGroup.checkedButton && tabGroup.buttons.length > 0 ? tabGroup.checkedButton.tabIndex : -1
/**
* @brief This property holds the number of tab buttons.
*/
readonly property int count: tabGroup.buttons.length
/**
* @brief This property holds the ButtonGroup used to manage the tabs.
*/
readonly property T.ButtonGroup tabGroup: tabGroup
/**
* @brief This property holds the calculated width that buttons on the tab bar use.
*
* @since 5.102
*/
property real buttonWidth: {
// Counting buttons because Repeaters can be counted among visibleChildren
let visibleButtonCount = 0;
const minWidth = contentItem.height * 0.75;
for (const visibleChild of contentItem.visibleChildren) {
if (contentItem.width / visibleButtonCount >= minWidth && // make buttons go off the screen if there is physically no room for them
visibleChild instanceof T.AbstractButton) { // Checking for AbstractButtons because any AbstractButton can act as a tab
++visibleButtonCount;
}
}
return Math.round(contentItem.width / visibleButtonCount);
}
//END properties
onCurrentIndexChanged: {
if (currentIndex === -1) {
if (tabGroup.checkState !== Qt.Unchecked) {
tabGroup.checkState = Qt.Unchecked;
}
return;
}
if (!tabGroup.checkedButton || tabGroup.checkedButton.tabIndex !== currentIndex) {
const buttonForCurrentIndex = tabGroup.buttons[currentIndex]
if (buttonForCurrentIndex.action) {
// trigger also toggles and causes clicked() to be emitted
buttonForCurrentIndex.action.trigger();
} else {
// toggle() does not trigger the action,
// so don't use it if you want to use an action.
// It also doesn't cause clicked() to be emitted.
buttonForCurrentIndex.toggle();
}
}
}
// ensure that by default, we do not have unintended padding and spacing from the style
spacing: 0
padding: 0
topPadding: undefined
leftPadding: undefined
rightPadding: undefined
bottomPadding: undefined
verticalPadding: undefined
// Using Math.round() on horizontalPadding can cause the contentItem to jitter left and right when resizing the window.
horizontalPadding: Math.floor(Math.max(0, width - root.maximumContentWidth) / 2)
contentWidth: Math.ceil(Math.min(root.availableWidth, root.maximumContentWidth))
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
position: {
if (QQC2.ApplicationWindow.window?.footer === root) {
return QQC2.ToolBar.Footer
} else if (parent?.footer === root) {
return QQC2.ToolBar.Footer
} else if (parent?.parent?.footer === parent) {
return QQC2.ToolBar.Footer
} else {
return QQC2.ToolBar.Header
}
}
contentItem: RowLayout {
id: rowLayout
spacing: root.spacing
}
// Used to manage which tab is checked and change the currentIndex
T.ButtonGroup {
id: tabGroup
exclusive: true
buttons: root.contentItem.children.filter((child) => child !== instantiator)
onCheckedButtonChanged: {
if (!checkedButton) {
return
}
if (root.currentIndex !== checkedButton.tabIndex) {
root.currentIndex = checkedButton.tabIndex;
}
}
}
// Using a Repeater here because Instantiator was causing issues:
// NavigationTabButtons that were supposed to be destroyed were still
// registered as buttons in tabGroup.
// NOTE: This will make Repeater show up as child through visibleChildren
Repeater {
id: instantiator
model: root.visibleActions
delegate: NavigationTabButton {
id: delegate
required property T.Action modelData
parent: root.contentItem
action: modelData
// Workaround setting the action when checkable is not explicitly set making tabs uncheckable
onActionChanged: action.checkable = true
Layout.minimumWidth: root.buttonWidth
Layout.maximumWidth: root.buttonWidth
Layout.fillHeight: true
Kirigami.Theme.textColor: root.Kirigami.Theme.textColor
Kirigami.Theme.backgroundColor: root.Kirigami.Theme.backgroundColor
Kirigami.Theme.highlightColor: root.Kirigami.Theme.highlightColor
}
}
}
@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief Navigation buttons to be used for the NavigationTabBar component.
*
* It supplies its own padding, and also supports using the QQC2 AbstractButton ``display`` property to be used in column lists.
*
* Alternative way to the "actions" property on NavigationTabBar, as it can be used
* with Repeater to generate buttons from models.
*
* Example usage:
* @code{.qml}
* Kirigami.NavigationTabBar {
* id: navTabBar
* Kirigami.NavigationTabButton {
* visible: true
* icon.name: "document-save"
* text: `test ${tabIndex + 1}`
* QQC2.ButtonGroup.group: navTabBar.tabGroup
* }
* Kirigami.NavigationTabButton {
* visible: false
* icon.name: "document-send"
* text: `test ${tabIndex + 1}`
* QQC2.ButtonGroup.group: navTabBar.tabGroup
* }
* actions: [
* Kirigami.Action {
* visible: true
* icon.name: "edit-copy"
* icon.height: 32
* icon.width: 32
* text: `test 3`
* checked: true
* },
* Kirigami.Action {
* visible: true
* icon.name: "edit-cut"
* text: `test 4`
* checkable: true
* },
* Kirigami.Action {
* visible: false
* icon.name: "edit-paste"
* text: `test 5`
* },
* Kirigami.Action {
* visible: true
* icon.source: "../logo.png"
* text: `test 6`
* checkable: true
* }
* ]
* }
* @endcode
*
* @since 5.87
* @since org.kde.kirigami 2.19
* @inherit QtQuick.Templates.TabButton
*/
T.TabButton {
id: control
/**
* @brief This property tells the index of this tab within the tab bar.
*/
readonly property int tabIndex: {
let tabIdx = 0
for (const child of parent.children) {
if (child === this) {
return tabIdx
}
// Checking for AbstractButtons because any AbstractButton can act as a tab
if (child instanceof T.AbstractButton) {
++tabIdx
}
}
return -1
}
// FIXME: all those internal properties should go, and the button should style itself in a more standard way
// probably similar to view items
readonly property color __foregroundColor: Kirigami.Theme.textColor
readonly property color __highlightForegroundColor: Kirigami.Theme.textColor
readonly property color __pressedColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.3)
readonly property color __hoverSelectColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.2)
readonly property color __checkedBorderColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.7)
readonly property color __pressedBorderColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.9)
readonly property real __verticalMargins: (display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.largeSpacing : 0
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
display: T.AbstractButton.TextUnderIcon
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
hoverEnabled: true
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
icon.height: display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
icon.width: display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
icon.color: checked ? __highlightForegroundColor : __foregroundColor
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: text
Accessible.onPressAction: control.action.trigger()
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button
Kirigami.Theme.inherit: false
implicitHeight: (control.display === T.AbstractButton.TextBesideIcon) ? 0 : (Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2)
color: "transparent"
Rectangle {
width: parent.width - Kirigami.Units.largeSpacing
height: parent.height - Kirigami.Units.largeSpacing
anchors.centerIn: parent
radius: Kirigami.Units.cornerRadius
color: control.down ? control.__pressedColor : (control.checked || control.hovered ? control.__hoverSelectColor : "transparent")
border.color: control.visualFocus ? control.__checkedBorderColor : (control.down ? control.__pressedBorderColor : color)
border.width: 1
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
Behavior on border.color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
}
}
contentItem: GridLayout {
columnSpacing: 0
rowSpacing: (label.visible && label.lineCount > 1) ? 0 : control.spacing
// if this is a row or a column
columns: control.display !== T.AbstractButton.TextBesideIcon ? 1 : 2
Kirigami.Icon {
id: icon
source: control.icon.name || control.icon.source
visible: (control.icon.name.length > 0 || control.icon.source.toString().length > 0) && control.display !== T.AbstractButton.TextOnly
color: control.icon.color
Layout.topMargin: control.__verticalMargins
Layout.bottomMargin: control.__verticalMargins
Layout.leftMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
Layout.rightMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
Layout.alignment: {
if (control.display === T.AbstractButton.TextBesideIcon) {
// row layout
return Qt.AlignVCenter | Qt.AlignRight;
} else {
// column layout
return Qt.AlignHCenter | ((!label.visible || label.lineCount > 1) ? Qt.AlignVCenter : Qt.AlignBottom);
}
}
implicitHeight: source ? control.icon.height : 0
implicitWidth: source ? control.icon.width : 0
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
}
QQC2.Label {
id: label
text: control.Kirigami.MnemonicData.richTextLabel
horizontalAlignment: (control.display === T.AbstractButton.TextBesideIcon) ? Text.AlignLeft : Text.AlignHCenter
visible: control.display !== T.AbstractButton.IconOnly
wrapMode: Text.Wrap
elide: Text.ElideMiddle
color: control.checked ? control.__highlightForegroundColor : control.__foregroundColor
font.bold: control.checked
font.pointSize: !icon.visible && control.display === T.AbstractButton.TextBelowIcon
? Kirigami.Theme.defaultFont.pointSize * 1.20 // 1.20 is equivalent to level 2 heading
: Kirigami.Theme.defaultFont.pointSize
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
Layout.topMargin: control.__verticalMargins
Layout.bottomMargin: control.__verticalMargins
Layout.alignment: {
if (control.display === T.AbstractButton.TextBesideIcon) {
// row layout
return Qt.AlignVCenter | Qt.AlignLeft;
} else {
// column layout
return icon.visible ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter;
}
}
Layout.fillWidth: true
Accessible.ignored: true
}
}
}
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as KP
import "templates" as KT
/**
* Overlay Drawers are used to expose additional UI elements needed for
* small secondary tasks for which the main UI elements are not needed.
* For example in Okular Mobile, an Overlay Drawer is used to display
* thumbnails of all pages within a document along with a search field.
* This is used for the distinct task of navigating to another page.
*
* @inherit org::kde::kirigami::templates::OverlayDrawer
*/
KT.OverlayDrawer {
id: root
//BEGIN Properties
focus: false
modal: true
drawerOpen: !modal
closePolicy: modal ? T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside : T.Popup.NoAutoClose
handleVisible: interactive && (modal || !drawerOpen) && (typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true)
// FIXME: set to false when it does not lead to blocking closePolicy.
// See Kirigami bug: 454119
interactive: true
onPositionChanged: {
if (!modal && !root.peeking && !root.animating) {
position = 1;
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Item {
parent: root.handle
anchors.fill: parent
Kirigami.ShadowedRectangle {
id: handleGraphics
anchors.centerIn: parent
Kirigami.Theme.colorSet: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.colorSet : Kirigami.Theme.Button
Kirigami.Theme.backgroundColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.backgroundColor : undefined
Kirigami.Theme.textColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.textColor : undefined
Kirigami.Theme.inherit: false
color: root.handle.pressed ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
visible: !parent.parent.handleAnchor || !parent.parent.handleAnchor.visible || root.handle.pressed || (root.modal && root.position > 0)
shadow.color: Qt.rgba(0, 0, 0, root.handle.pressed ? 0.6 : 0.4)
shadow.yOffset: 1
shadow.size: Kirigami.Units.gridUnit / 2
width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
height: width
radius: Kirigami.Units.cornerRadius
Behavior on color {
ColorAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Loader {
anchors.centerIn: handleGraphics
width: height
height: Kirigami.Units.iconSizes.smallMedium
Kirigami.Theme.colorSet: handleGraphics.Kirigami.Theme.colorSet
Kirigami.Theme.backgroundColor: handleGraphics.Kirigami.Theme.backgroundColor
Kirigami.Theme.textColor: handleGraphics.Kirigami.Theme.textColor
asynchronous: true
source: {
let edge = root.edge;
if (Qt.application.layoutDirection === Qt.RightToLeft) {
if (edge === Qt.LeftEdge) {
edge = Qt.RightEdge;
} else {
edge = Qt.LeftEdge;
}
}
if ((root.handleClosedIcon.source || root.handleClosedIcon.name)
&& (root.handleOpenIcon.source || root.handleOpenIcon.name)) {
return Qt.resolvedUrl("templates/private/GenericDrawerIcon.qml");
} else if (edge === Qt.LeftEdge) {
return Qt.resolvedUrl("templates/private/MenuIcon.qml");
} else if (edge === Qt.RightEdge && root instanceof Kirigami.ContextDrawer) {
return Qt.resolvedUrl("templates/private/ContextIcon.qml");
} else {
return "";
}
}
onItemChanged: {
if (item) {
item.drawer = root;
item.color = Qt.binding(() => root.handle.pressed
? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor);
}
}
}
}
Kirigami.Separator {
id: separator
LayoutMirroring.enabled: false
anchors {
top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
topMargin: segmentedSeparator.height
}
visible: !root.modal
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
}
Item {
id: segmentedSeparator
// an alternative to segmented style is full height
readonly property bool shouldUseSegmentedStyle: {
if (root.edge !== Qt.LeftEdge && root.edge !== Qt.RightEdge) {
return false;
}
if (root.collapsed) {
return false;
}
// compatible header
const header = root.header ?? null;
if (header instanceof T.ToolBar || header instanceof KT.AbstractApplicationHeader) {
return true;
}
// or compatible content
if (root.contentItem instanceof ColumnLayout && root.contentItem.children[0] instanceof T.ToolBar) {
return true;
}
return false;
}
anchors {
top: parent.top
left: separator.left
right: separator.right
}
height: {
if (root.edge !== Qt.LeftEdge && root.edge !== Qt.RightEdge) {
return 0;
}
if (typeof applicationWindow === "undefined") {
return 0;
}
const window = applicationWindow();
const globalToolBar = window.pageStack?.globalToolBar;
if (!globalToolBar) {
return 0;
}
return globalToolBar.preferredHeight;
}
visible: separator.visible
Kirigami.Separator {
LayoutMirroring.enabled: false
anchors {
fill: parent
topMargin: segmentedSeparator.shouldUseSegmentedStyle ? Kirigami.Units.largeSpacing : 0
bottomMargin: segmentedSeparator.shouldUseSegmentedStyle ? Kirigami.Units.largeSpacing : 0
}
Behavior on anchors.topMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on anchors.bottomMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
}
}
KP.EdgeShadow {
z: -2
visible: root.modal
edge: root.edge
anchors {
right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
}
opacity: root.position === 0 ? 0 : 1
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
}
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import org.kde.kirigami as Kirigami
import "private" as P
import "templates" as T
/**
* @brief An overlay sheet that covers the current Page content.
*
* Its contents can be scrolled up or down, scrolling all the way up or
* all the way down, dismisses it.
* Use this for big, modal dialogs or information display, that can't be
* logically done as a new separate Page, even if potentially
* are taller than the screen space.
* @inherit org::kde::kirigami::templates::OverlaySheet
*/
T.OverlaySheet {
id: root
background: P.DefaultCardBackground {
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
Kirigami.Theme.inherit: false
}
}
@@ -0,0 +1,299 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as P
/**
* Page is a container for all the app pages: everything pushed to the
* ApplicationWindow's pageStack should be a Page.
*
* @see ScrollablePage
* For content that should be scrollable, such as ListViews, use ScrollablePage instead.
* @inherit QtQuick.Controls.Page
*/
QQC2.Page {
id: root
//BEGIN properties
padding: Kirigami.Units.gridUnit
/**
* @brief 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.
*
* Use this if your flickable has some non standard properties, such as not covering the whole Page.
*
* @see ScrollablePage
*/
property Flickable flickable
/**
* @brief This property holds the actions for the page.
*
* These actions will be displayed in the toolbar on the desktop and inside
* the ContextDrawer on mobile.
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.Page {
* actions: [
* Kirigami.Action {...},
* Kirigami.Action {...}
* }
* }
* @endcode
*/
property list<T.Action> actions
/**
* @brief This property tells us if it is the currently active page.
*
* Specifies if it's the currently selected page in the window's pages row, or if layers
* are used whether this is the topmost item on the layers stack. If the page is
* not attached to either a column view or a stack view, expect this to be true.
*
* @since 2.1
*/
//TODO KF6: remove this or at least all the assumptions about the internal tree structure of items
// Kirigami.ColumnView.view.parent.parent is the StackView in which the ColumnView is, the condition means "is the ColumnView the current layer of the pagerow"
readonly property bool isCurrentPage: Kirigami.ColumnView.view
? (Kirigami.ColumnView.index === Kirigami.ColumnView.view.currentIndex && Kirigami.ColumnView.view.parent.parent.currentItem === Kirigami.ColumnView.view.parent)
: (parent && parent instanceof QQC2.StackView
? parent.currentItem === root
: true)
/**
* An item which stays on top of every other item in the page,
* if you want to make sure some elements are completely in a
* layer on top of the whole content, parent items to this one.
* It's a "local" version of ApplicationWindow's overlay
*
* @property Item overlay
* @since 2.5
*/
readonly property alias overlay: overlayItem
/**
* @brief This holds the icon that represents this page.
* @property var icon
*/
property P.ActionIconGroup icon: P.ActionIconGroup {}
/**
* @brief Progress of a task this page is doing.
*
* Set to undefined to indicate that there are no ongoing tasks.
*
* default: ``undefined``
*
* @property real progress
*/
property var progress: undefined
/**
* @brief The delegate which will be used to draw the page title.
*
* It can be customized to put any kind of Item in there.
*
* @since 2.7
*/
property Component titleDelegate: Component {
id: defaultTitleDelegate
P.DefaultPageTitleDelegate {
text: root.title
}
}
/**
* The item used as global toolbar for the page
* present only if we are in a PageRow as a page or as a layer,
* and the style is either Titles or ToolBar.
*
* @since 2.5
*/
readonly property Item globalToolBarItem: globalToolBar.item
/**
* The style for the automatically generated global toolbar: by default the Page toolbar is the one set globally in the PageRow in its globalToolBar.style property.
* A single page can override the application toolbar style for itself.
* It is discouraged to use this, except very specific exceptions, like a chat
* application which can't have controls on the bottom except the text field.
* If the Page is not in a PageRow, by default the toolbar will be invisible,
* so has to be explicitly set to Kirigami.ApplicationHeaderStyle.ToolBar if
* desired to be used in that case.
*/
property int globalToolBarStyle: {
if (globalToolBar.row && !globalToolBar.stack) {
return globalToolBar.row.globalToolBar.actualStyle;
} else if (globalToolBar.stack) {
return Kirigami.ApplicationHeaderStyle.ToolBar;
} else {
return Kirigami.ApplicationHeaderStyle.None;
}
}
//END properties
//BEGIN signal and signal handlers
/**
* @brief 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);
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
// FIXME: on material the shadow would bleed over
clip: root.header !== null;
Component.onCompleted: {
headerChanged();
parentChanged(root.parent);
globalToolBar.syncSource();
bottomToolBar.pageComplete = true
}
onParentChanged: {
if (!parent) {
return;
}
globalToolBar.stack = null;
globalToolBar.row = null;
if (root.Kirigami.ColumnView.view) {
globalToolBar.row = root.Kirigami.ColumnView.view.__pageRow;
}
if (root.T.StackView.view) {
globalToolBar.stack = root.T.StackView.view;
globalToolBar.row = root.T.StackView.view.parent instanceof Kirigami.PageRow ? root.T.StackView.view.parent : null;
}
if (globalToolBar.row) {
root.globalToolBarStyleChanged.connect(globalToolBar.syncSource);
globalToolBar.syncSource();
}
}
//END signals and signal handlers
// in data in order for them to not be considered for contentItem, contentChildren, contentData
data: [
Item {
id: overlayItem
parent: root
z: 9997
anchors {
fill: parent
topMargin: globalToolBar.height
}
}
]
// global top toolbar if we are in a PageRow (in the row or as a layer)
Kirigami.ColumnView.globalHeader: Loader {
id: globalToolBar
z: 9999
height: item ? item.implicitHeight : 0
width: root.width
property Kirigami.PageRow row
property T.StackView stack
// don't load async so that on slower devices we don't have the page content height changing while loading in
// otherwise, it looks unpolished and jumpy
asynchronous: false
visible: active
active: (root.titleDelegate !== defaultTitleDelegate || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.Titles)
onActiveChanged: {
if (active) {
syncSource();
}
}
function syncSource() {
if (root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar &&
root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.Titles &&
root.titleDelegate !== defaultTitleDelegate) {
sourceComponent = root.titleDelegate;
} else if (active) {
const url = root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
? "private/globaltoolbar/ToolBarPageHeader.qml"
: "private/globaltoolbar/TitlesPageHeader.qml";
// TODO: find container reliably, remove assumption
setSource(Qt.resolvedUrl(url), {
pageRow: Qt.binding(() => row),
page: root,
current: Qt.binding(() => {
if (!row && !stack) {
return true;
} else if (stack) {
return stack;
} else {
return row.currentIndex === root.Kirigami.ColumnView.level;
}
}),
});
}
}
}
// bottom action buttons
Kirigami.ColumnView.globalFooter: Loader {
id: bottomToolBar
property T.Page page: root
property bool pageComplete: false
visible: active
active: {
// Important! Do not do anything until the page has been
// completed, so we are sure what the globalToolBarStyle is,
// otherwise we risk creating the content and then discarding it.
if (!pageComplete) {
return false;
}
if ((globalToolBar.row && globalToolBar.row.globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar)
|| root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
|| root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.None) {
return false;
}
if (root.actions.length === 0) {
return false;
}
// Legacy
if (typeof applicationWindow === "undefined") {
return true;
}
const drawer = applicationWindow() ? applicationWindow()['contextDrawer'] : undefined;
if (Boolean(drawer) && drawer.enabled && drawer.handleVisible) {
return false;
}
return true;
}
source: Qt.resolvedUrl("./private/globaltoolbar/ToolBarPageFooter.qml")
}
Layout.fillWidth: true
}
@@ -0,0 +1,238 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* An action used to load Pages coming from a common PagePool
* in a PageRow or QtQuickControls2 StackView.
*
* @see PagePool
*/
Kirigami.Action {
id: root
//BEGIN properties
/**
* @brief This property holds the url or filename of the page that this action will load.
*/
property string page
/**
* @brief This property holds the PagePool object used by this PagePoolAction.
*
* PagePool will make sure only one instance of the page identified by the page url will be created and reused.
* PagePool's lastLoaderUrl property will be used to control the mutual exclusivity of the checked
* state of the PagePoolAction instances sharing the same PagePool.
*/
property Kirigami.PagePool pagePool
/**
* The pageStack property accepts either a Kirigami.PageRow or a QtQuickControls2 StackView.
* The component that will instantiate the pages, which has to work with a stack logic.
* Kirigami.PageRow is recommended, but will work with QtQuicControls2 StackView as well.
*
* default: `bound to ApplicationWindow's global pageStack, which is a PageRow by default`
*/
property Item pageStack: typeof applicationWindow !== 'undefined' ? applicationWindow().pageStack : null
/**
* @brief This property sets the page in the pageStack after which
* new pages will be pushed.
*
* All pages present after the given basePage will be removed from the pageStack
*/
property T.Page basePage
/**
* This property holds a function that generate the property values for the created page
* when it is pushed onto the Kirigami.PagePool.
*
* Example usage:
* @code{.qml}
* Kirigami.PagePoolAction {
* text: i18n("Security")
* icon.name: "security-low"
* page: Qt.resolvedUrl("Security.qml")
* initialProperties: {
* return {
* room: root.room
* }
* }
* }
* @endcode
* @property QVariantMap initialProperties
*/
property var initialProperties
/**
* @brief This property sets whether PagePoolAction will use the layers property
* implemented by the pageStack.
*
* This is intended for use with PageRow layers to allow PagePoolActions to
* push context-specific pages onto the layers stack.
*
* default: ``false``
*
* @since 5.70
* @since org.kde.kirigami 2.12
*/
property bool useLayers: false
//END properties
/**
* @returns the page item held in the PagePool or null if it has not been loaded yet.
*/
function pageItem(): Item {
return pagePool.pageForUrl(page)
}
/**
* @returns true if the page has been loaded and placed on pageStack.layers
* and useLayers is true, otherwise returns null.
*/
function layerContainsPage(): bool {
if (!useLayers || !pageStack.hasOwnProperty("layers")) {
return false;
}
const pageItem = this.pageItem();
const item = pageStack.layers.find(item => item === pageItem);
return item !== null;
}
/**
* @returns true if the page has been loaded and placed on the pageStack,
* otherwise returns null.
*/
function stackContainsPage(): bool {
if (useLayers) {
return false;
}
return pageStack.columnView.containsItem(pagePool.pageForUrl(page));
}
checkable: true
onTriggered: {
if (page.length === 0 || !pagePool || !pageStack) {
return;
}
// User intends to "go back" to this layer.
const pageItem = this.pageItem();
if (layerContainsPage() && pageItem !== pageStack.layers.currentItem) {
pageStack.layers.replace(pageItem, pageItem); // force pop above
return;
}
// User intends to "go back" to this page.
if (stackContainsPage()) {
if (pageStack.hasOwnProperty("layers")) {
pageStack.layers.clear();
}
}
const stack = useLayers ? pageStack.layers : pageStack
if (pageItem != null && stack.currentItem == pageItem) {
return;
}
if (initialProperties && typeof(initialProperties) !== "object") {
console.warn("initialProperties must be of type object");
return;
}
if (!stack.hasOwnProperty("pop") || typeof stack.pop !== "function" || !stack.hasOwnProperty("push") || typeof stack.push !== "function") {
return;
}
if (pagePool.isLocalUrl(page)) {
if (basePage) {
stack.pop(basePage);
} else if (!useLayers) {
stack.clear();
}
stack.push(initialProperties
? pagePool.loadPageWithProperties(page, initialProperties)
: pagePool.loadPage(page));
} else {
const callback = item => {
if (basePage) {
stack.pop(basePage);
} else if (!useLayers) {
stack.clear();
}
stack.push(item);
};
if (initialProperties) {
pagePool.loadPage(page, initialProperties, callback);
} else {
pagePool.loadPage(page, callback);
}
}
}
// Exposing this as a property is required as Action does not have a default property
property QtObject _private: QtObject {
id: _private
function setChecked(checked) {
root.checked = checked;
}
function clearLayers() {
root.pageStack.layers.clear();
}
property list<Connections> connections: [
Connections {
target: root.pageStack
function onCurrentItemChanged() {
if (root.useLayers) {
if (root.layerContainsPage()) {
_private.clearLayers();
}
if (root.checkable) {
_private.setChecked(false);
}
} else {
if (root.checkable) {
_private.setChecked(root.stackContainsPage());
}
}
}
},
Connections {
enabled: root.pageStack.hasOwnProperty("layers")
target: root.pageStack.layers
function onCurrentItemChanged() {
if (root.useLayers && root.checkable) {
_private.setChecked(root.layerContainsPage());
} else {
if (root.pageStack.layers.depth === 1 && root.stackContainsPage()) {
_private.setChecked(true);
}
}
}
}
]
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is a standard password text field.
*
* Example usage:
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
* Kirigami.PasswordField {
* id: passwordField
* onAccepted: {
* // check if passwordField.text is valid
* }
* }
* @endcode
*
* @inherit org::kde::kirgami::ActionTextField
* @since 5.57
* @author Carl Schwan <carl@carlschwan.eu>
*/
Kirigami.ActionTextField {
id: root
/**
* @brief This property tells whether the password will be displayed in cleartext rather than obfuscated.
*
* default: ``false``
*
* @since 5.57
*/
property bool showPassword: false
echoMode: root.showPassword ? TextInput.Normal : TextInput.Password
placeholderText: qsTr("Password")
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhSensitiveData
rightActions: Kirigami.Action {
text: root.showPassword ? qsTr("Hide Password") : qsTr("Show Password")
icon.name: root.showPassword ? "password-show-off" : "password-show-on"
onTriggered: root.showPassword = !root.showPassword
}
Keys.onPressed: event => {
if (event.matches(StandardKey.Undo)) {
// Disable undo action for security reasons
// See QTBUG-103934
event.accepted = true
}
}
}
@@ -0,0 +1,298 @@
/*
* SPDX-FileCopyrightText: 2020 Nate Graham <nate@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief A placeholder message indicating that a view is empty.
*
* The message comprises a label with text, an optional explanation below the main text,
* an optional icon above all the text, and an optional button below all the text which
* can be used to easily show the user what to do next to add content to the view.
*
* The explanatory text is selectable and can contain clickable links. In this latter
* case, client code must implement an ``onLinkactivated:`` signal handler or the links
* will not work.
*
* The top-level component is a ColumnLayout, so additional components items can
* simply be added as child items and they will be positioned sanely.
*
* Example usage:
* @code{.qml}
** used as a "this view is empty" message
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
* model: [...]
* delegate: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: listView.count === 0
*
* text: "There are no items in this list"
* }
* }
* @endcode
* @code{.qml}
** Used as a "here's how to proceed" message
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
* model: [...]
* delegate: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: listView.count === 0
*
* text: "Add an item to proceed"
*
* helpfulAction: Kirigami.Action {
* icon.name: "list-add"
* text: "Add item..."
* onTriggered: {
* [...]
* }
* }
* }
* [...]
* }
* @endcode
* @code{.qml}
** Used as a "there was a problem here" message
* import org.kde.kirigami as Kirigami
*
* Kirigami.Page {
* id: root
* readonly property bool networkConnected: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: root.networkConnected
*
* icon.name: "network-disconnect"
* text: "Unable to load content"
* explanation: "Please try again later."
* " Visit <a href="https://foo.com/com>this link</a> for more details."
* onLinkActivated: link => Qt.openUrlExternally(link)
* }
* }
* @endcode
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
** Used as a "Here's what you do next" button
* Kirigami.Page {
* id: root
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: root.loading
*
* helpfulAction: Kirigami.Action {
* icon.name: "list-add"
* text: "Add item..."
* onTriggered: {
* [...]
* }
* }
* }
* }
* @endcode
* @inherit QtQuick.Layouts.ColumnLayout
* @since 2.12
*/
ColumnLayout {
id: root
enum Type {
Actionable,
Informational
}
//BEGIN properties
/**
* @brief This property holds the PlaceholderMessage type.
*
* The type of the message. This can be:
* * ``Kirigami.PlaceholderMessage.Type.Actionable``: Makes it more attention-getting. Useful when the user is expected to interact with the message.
* * ``Kirigami.PlaceholderMessage.Type.Informational``: Makes it less prominent. Useful when the message in only informational.
*
* default: `if a helpfulAction is provided this will be of type Actionable otherwise of type Informational.`
*
* @since 5.94
*/
property int type: actionButton.action?.enabled
? PlaceholderMessage.Type.Actionable
: PlaceholderMessage.Type.Informational
/**
* @brief This property holds the text to show in the placeholder label.
*
* Optional; if not defined, the message will have no large text label
* text. If both text: and explanation: are omitted, the message will have
* no text and only an icon, action button, and/or other custom content.
*
* @since 5.70
*/
property string text
/**
* @brief This property holds the smaller explanatory text to show below the larger title-style text
*
* Useful for providing a user-friendly explanation on how to proceed.
*
* Optional; if not defined, the message will have no supplementary
* explanatory text.
*
* @since 5.80
*/
property string explanation
/**
* @brief This property provides an icon to display above the top text label.
* @note It accepts ``icon.name`` and ``icon.source`` to set the icon source.
* It is suggested to use ``icon.name``.
*
* Optional; if undefined, the message will have no icon.
* Falls back to `undefined` if the specified icon is not valid or cannot
* be loaded.
*
* @see org::kde::kirigami::private::ActionIconGroup
* @since 5.70
*/
readonly property P.ActionIconGroup icon: P.ActionIconGroup {
width: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
height: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
color: Kirigami.Theme.textColor
}
/**
* @brief This property holds an action that helps the user proceed.
*
* Typically used to guide the user to the next step for adding
* content or items to an empty view.
*
* Optional; if undefined, no button will appear below the text label.
*
* @property QtQuick.Controls.Action helpfulAction
* @since 5.70
*/
property alias helpfulAction: actionButton.action
/**
* This property holds the link embedded in the explanatory message text that
* the user is hovering over.
*/
readonly property alias hoveredLink: label.hoveredLink
/**
* This signal is emitted when a link is hovered in the explanatory message
* text.
* @param The hovered link.
*/
signal linkHovered(string link)
/**
* This signal is emitted when a link is clicked or tapped in the explanatory
* message text.
* @param The clicked or tapped link.
*/
signal linkActivated(string link)
//END properties
spacing: Kirigami.Units.largeSpacing
Component.onCompleted: _announce();
onVisibleChanged: {
_announce();
}
function _announce()
{
if (visible && Accessible.announce) {
// Accessible.announce was added in Qt 6.8.0
if (root.text.length > 0)
Accessible.announce(root.text);
if (root.explanation.length > 0)
Accessible.announce(root.explanation);
}
}
Kirigami.Icon {
visible: source !== undefined
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.5
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: root.icon.width
Layout.preferredHeight: root.icon.height
color: root.icon.color
source: {
if (root.icon.source.length > 0) {
return root.icon.source
} else if (root.icon.name.length > 0) {
return root.icon.name
}
return undefined
}
}
Kirigami.Heading {
text: root.text
visible: text.length > 0
type: Kirigami.Heading.Primary
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
wrapMode: Text.WordWrap
}
Kirigami.SelectableLabel {
id: label
text: root.explanation
visible: root.explanation.length > 0
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
Layout.fillWidth: true
onLinkHovered: link => root.linkHovered(link)
onLinkActivated: link => root.linkActivated(link)
}
QQC2.Button {
id: actionButton
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.gridUnit
visible: action?.enabled ?? false
}
}
@@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates as KT
import "private"
// TODO KF6: undo many workarounds to make existing code work?
/**
* @brief ScrollablePage is a Page that holds scrollable content, such as a ListView.
*
* Scrolling and scrolling indicators will be automatically managed.
*
* Example usage:
* @code
* ScrollablePage {
* id: root
* // The page will automatically be scrollable
* Rectangle {
* width: root.width
* height: 99999
* }
* }
* @endcode
*
* @warning Do not put a ScrollView inside of a ScrollablePage; children of a ScrollablePage are already inside a ScrollView.
*
* Another behavior added by this class is a "scroll down to refresh" behavior
* It also can give the contents of the flickable to have more top margins in order
* to make possible to scroll down the list to reach it with the thumb while using the
* phone with a single hand.
*
* Implementations should handle the refresh themselves as follows
*
* Example usage:
* @code
* Kirigami.ScrollablePage {
* id: view
* supportsRefreshing: true
* onRefreshingChanged: {
* if (refreshing) {
* myModel.refresh();
* }
* }
* ListView {
* // NOTE: MyModel doesn't come from the components,
* // it's purely an example on how it can be used together
* // some application logic that can update the list model
* // and signals when it's done.
* model: MyModel {
* onRefreshDone: view.refreshing = false;
* }
* delegate: ItemDelegate {}
* }
* }
* [...]
* @endcode
*/
Kirigami.Page {
id: root
//BEGIN properties
/**
* @brief This property tells whether the list is asking for a refresh.
*
* This property will automatically be set to true when the user pulls the list down enough,
* which in return, shows a loading spinner. When this is set to true, it signals
* the application logic to start its refresh procedure.
*
* default: ``false``
*
* @note The application itself will have to set back this property to false when done.
*/
property bool refreshing: false
/**
* @brief This property sets whether scrollable page supports "pull down to refresh" behaviour.
*
* default: ``false``
*/
property bool supportsRefreshing: false
/**
* @brief This property holds the main Flickable item of this page.
* @deprecated here for compatibility; will be removed in KF6.
*/
property Flickable flickable: Flickable {} // FIXME KF6: this empty flickable exists for compatibility reasons. some apps assume flickable exists right from the beginning but ScrollView internally assumes it does not
onFlickableChanged: scrollView.contentItem = flickable;
/**
* @brief This property sets the vertical scrollbar policy.
* @property Qt::ScrollBarPolicy verticalScrollBarPolicy
*/
property int verticalScrollBarPolicy
/**
* @brief Set if the vertical scrollbar should be interactable.
* @property bool verticalScrollBarInteractive
*/
property bool verticalScrollBarInteractive: true
/**
* @brief This property sets the horizontal scrollbar policy.
* @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
*/
property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
/**
* @brief Set if the horizontal scrollbar should be interactable.
* @property bool horizontalScrollBarInteractive
*/
property bool horizontalScrollBarInteractive: true
default property alias scrollablePageData: itemsParent.data
property alias scrollablePageChildren: itemsParent.children
/*
* @deprecated here for compatibility; will be removed in KF6.
*/
property QtObject mainItem
onMainItemChanged: {
print("Warning: the mainItem property is deprecated");
scrollablePageData.push(mainItem);
}
/**
* @brief This property sets whether it is possible to navigate the items in a view that support it.
*
* If true, and if flickable is an item view (e.g. ListView, GridView), it will be possible
* to navigate the view current items with keyboard up/down arrow buttons.
* Also, any key event will be forwarded to the current list item.
*
* default: ``true``
*/
property bool keyboardNavigationEnabled: true
//END properties
implicitWidth: flickable?.contentItem?.implicitWidth
?? Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding,
implicitHeaderWidth,
implicitFooterWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
contentHeight: flickable?.contentHeight ?? 0
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: flickable?.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
Keys.forwardTo: {
if (root.keyboardNavigationEnabled && root.flickable) {
if (("currentItem" in root.flickable) && root.flickable.currentItem) {
return [ root.flickable.currentItem, root.flickable ];
} else {
return [ root.flickable ];
}
} else {
return [];
}
}
contentItem: QQC2.ScrollView {
id: scrollView
anchors {
top: root.header?.visible
? root.header.bottom
: parent.top
bottom: root.footer?.visible ? root.footer.top : parent.bottom
left: parent.left
right: parent.right
}
clip: true
QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
QQC2.ScrollBar.horizontal.interactive: root.horizontalScrollBarInteractive
QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
QQC2.ScrollBar.vertical.interactive: root.verticalScrollBarInteractive
}
data: [
// Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
MouseArea {
id: scrollingArea
width: root.horizontalScrollBarPolicy === QQC2.ScrollBar.AlwaysOff ? root.flickable.width : Math.max(root.flickable.width, implicitWidth)
height: Math.max(root.flickable.height, implicitHeight)
implicitWidth: {
let implicit = 0;
for (const child of itemsParent.visibleChildren) {
if (child.implicitWidth <= 0) {
implicit = Math.max(implicit, child.width);
} else {
implicit = Math.max(implicit, child.implicitWidth);
}
}
return implicit + itemsParent.anchors.leftMargin + itemsParent.anchors.rightMargin;
}
implicitHeight: {
let implicit = 0;
for (const child of itemsParent.visibleChildren) {
if (child.implicitHeight <= 0) {
implicit = Math.max(implicit, child.height);
} else {
implicit = Math.max(implicit, child.implicitHeight);
}
}
return implicit + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
}
Item {
id: itemsParent
property Flickable flickable
anchors {
fill: parent
topMargin: root.topPadding
leftMargin: root.leftPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
onChildrenChanged: {
const child = children[children.length - 1];
if (child instanceof QQC2.ScrollView) {
print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
}
}
}
Binding {
target: root.flickable
property: "bottomMargin"
value: root.bottomPadding
restoreMode: Binding.RestoreBinding
}
},
Loader {
id: busyIndicatorLoader
active: root.supportsRefreshing
sourceComponent: PullDownIndicator {
parent: root
active: root.refreshing
onTriggered: root.refreshing = true
}
}
]
Component.onCompleted: {
let flickableFound = false;
for (const child of itemsParent.data) {
if (child instanceof Flickable) {
// If there were more flickable children, take the last one, as behavior compatibility
// with old internal ScrollView
child.activeFocusOnTab = true;
root.flickable = child;
flickableFound = true;
if (child instanceof ListView) {
child.keyNavigationEnabled = true;
child.keyNavigationWraps = false;
}
} else if (child instanceof Item) {
child.anchors.left = itemsParent.left;
child.anchors.right = itemsParent.right;
} else if (child instanceof KT.OverlaySheet) {
// Reparent sheets, needs to be done before Component.onCompleted
if (child.parent === itemsParent || child.parent === null) {
child.parent = root;
}
}
}
if (flickableFound) {
scrollView.contentItem = root.flickable;
root.flickable.parent = scrollView;
// The flickable needs focus only if the page didn't already explicitly set focus to some other control (eg a text field in the header)
Qt.callLater(() => {
if (root.activeFocus) {
root.flickable.forceActiveFocus();
}
});
// Some existing code incorrectly uses anchors
root.flickable.anchors.fill = undefined;
root.flickable.anchors.top = undefined;
root.flickable.anchors.left = undefined;
root.flickable.anchors.right = undefined;
root.flickable.anchors.bottom = undefined;
scrollingArea.visible = false;
} else {
scrollView.contentItem = root.flickable;
scrollingArea.parent = root.flickable.contentItem;
scrollingArea.visible = true;
root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
scrollView.forceActiveFocus(Qt.TabFocusReason); // QTBUG-44043 : Focus on currentItem instead of pageStack itself
}
root.flickable.flickableDirection = Flickable.VerticalFlick;
// HACK: Qt's default flick deceleration is too high, and we can't change it from plasma-integration, see QTBUG-121500
root.flickable.flickDeceleration = 1500;
root.flickable.maximumFlickVelocity = 5000;
}
}
@@ -0,0 +1,132 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
* SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is a standard TextField following the KDE HIG, which, by default,
* uses Ctrl+F as the focus keyboard shortcut and "Search…" as a placeholder text.
*
* Example usage for the search field component:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.SearchField {
* id: searchField
* onAccepted: console.log("Search text is " + searchField.text)
* }
* @endcode
*
* @inherit org::kde::kirigami::ActionTextField
*/
Kirigami.ActionTextField {
id: root
/**
* @brief This property sets whether the accepted signal is fired automatically
* when the text is changed.
*
* Setting this to false will require that the user presses return or enter
* (the same way a QtQuick.Controls.TextInput works).
*
* default: ``true``
*
* @since 5.81
* @since org.kde.kirigami 2.16
*/
property bool autoAccept: true
/**
* @brief This property sets whether to delay automatic acceptance of the search input.
*
* Set this to true if your search is expensive (such as for online
* operations or in exceptionally slow data sets) and want to delay it
* for 2.5 seconds.
*
* @note If you must have immediate feedback (filter-style), use the
* text property directly instead of accepted()
*
* default: ``false``
*
* @since 5.81
* @since org.kde.kirigami 2.16
*/
property bool delaySearch: false
// padding to accommodate search icon nicely
leftPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
return _rightActionsRow.width + Kirigami.Units.smallSpacing
} else {
return searchIcon.width + Kirigami.Units.smallSpacing * 3
}
rightPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
return searchIcon.width + Kirigami.Units.smallSpacing * 3
} else {
return _rightActionsRow.width + Kirigami.Units.smallSpacing
}
Kirigami.Icon {
id: searchIcon
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.left: root.left
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.verticalCenter: root.verticalCenter
anchors.verticalCenterOffset: Math.round((root.topPadding - root.bottomPadding) / 2)
implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
color: root.placeholderTextColor
source: "search"
}
placeholderText: qsTr("Search…")
Accessible.name: qsTr("Search")
Accessible.searchEdit: true
focusSequence: StandardKey.Find
inputMethodHints: Qt.ImhNoPredictiveText
EnterKey.type: Qt.EnterKeySearch
rightActions: [
Kirigami.Action {
//ltr confusingly refers to the direction of the arrow in the icon, not the text direction which it should be used in
icon.name: root.effectiveHorizontalAlignment === TextInput.AlignRight ? "edit-clear-locationbar-ltr" : "edit-clear-locationbar-rtl"
visible: root.text.length > 0
text: qsTr("Clear search")
onTriggered: {
root.clear();
// Since we are always sending the accepted signal here (whether or not the user has requested
// that the accepted signal be delayed), stop the delay timer that gets started by the text changing
// above, so that we don't end up sending two of those in rapid succession.
fireSearchDelay.stop();
root.accepted();
}
}
]
Timer {
id: fireSearchDelay
interval: root.delaySearch ? Kirigami.Units.humanMoment : Kirigami.Units.shortDuration
running: false
repeat: false
onTriggered: {
if (root.acceptableInput) {
root.accepted();
}
}
}
onAccepted: {
fireSearchDelay.running = false
}
onTextChanged: {
if (root.autoAccept) {
fireSearchDelay.restart();
} else {
fireSearchDelay.stop();
}
}
}
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
* SPDX-FileCopyrightText: 2024 Akseli Lahtinen <akselmo@akselmo.dev>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
/**
* @brief This is a label which supports text selection.
*
* The label uses TextEdit component, which is wrapped inside a Control component.
*
*
* Example usage:
* @code{.qml}
* Kirigami.SelectableLabel {
* text: "Label"
* }
* @endcode
*
* @see https://bugreports.qt.io/browse/QTBUG-14077
* @since org.kde.kirigami 2.20
* @since 5.95
* @inherit QtQuick.Control
*/
QQC2.Control {
id: root
//TODO KF7: Cleanup from unnecessary properties we dont need to expose for a label
activeFocusOnTab: false
padding: undefined
topPadding: undefined
leftPadding: undefined
rightPadding: undefined
bottomPadding: undefined
Accessible.name: textEdit.text
Accessible.role: Accessible.StaticText
Accessible.selectableText: true
Accessible.editable: false
property bool contextMenuEnabled: true
property alias readOnly: textEdit.readOnly
property alias selectByMouse: textEdit.selectByMouse
property alias color: textEdit.color
property alias selectedTextColor: textEdit.selectedTextColor
property alias selectionColor: textEdit.selectionColor
property alias text: textEdit.text
property alias baseUrl: textEdit.baseUrl
property var cursorShape
property alias horizontalAlignment: textEdit.horizontalAlignment
property alias verticalAlignment: textEdit.verticalAlignment
property alias textFormat: textEdit.textFormat
property alias wrapMode: textEdit.wrapMode
property alias activeFocusOnPress: textEdit.activeFocusOnPress
property alias cursorDelegate: textEdit.cursorDelegate
property alias cursorPosition: textEdit.cursorPosition
property alias cursorVisible: textEdit.cursorVisible
property alias inputMethodHints: textEdit.inputMethodHints
property alias mouseSelectionMode: textEdit.mouseSelectionMode
property alias overwriteMode: textEdit.overwriteMode
property alias persistentSelection: textEdit.persistentSelection
property alias renderType: textEdit.renderType
property alias selectByKeyboard: textEdit.selectByKeyboard
property alias tabStopDistance: textEdit.tabStopDistance
property alias textMargin: textEdit.textMargin
readonly property alias canPaste: textEdit.canPaste
readonly property alias canRedo: textEdit.canRedo
readonly property alias canUndo: textEdit.canUndo
readonly property alias inputMethodComposing: textEdit.inputMethodComposing
readonly property alias length: textEdit.length
readonly property alias lineCount: textEdit.lineCount
readonly property alias selectionEnd: textEdit.selectionEnd
readonly property alias selectionStart: textEdit.selectionStart
readonly property alias contentHeight: textEdit.contentHeight
readonly property alias contentWidth: textEdit.contentWidth
readonly property alias hoveredLink: textEdit.hoveredLink
readonly property alias preeditText: textEdit.preeditText
readonly property alias selectedText: textEdit.selectedText
readonly property alias cursorRectangle: textEdit.cursorRectangle
readonly property alias cursorSelection: textEdit.cursorSelection
readonly property alias effectiveHorizontalAlignment: textEdit.effectiveHorizontalAlignment
readonly property alias textDocument: textEdit.textDocument
signal editingFinished()
signal clicked()
signal linkActivated(string link)
signal linkHovered(string link)
//BEGIN TextArea dummy entries
property var flickable: undefined
property var placeholderText: undefined
property var placeholderTextColor: undefined
signal pressAndHold(MouseEvent event)
signal pressed(MouseEvent event)
signal released(MouseEvent event)
//END TextArea dummy entries
contentItem: TextEdit {
id: textEdit
/**
* @brief This property holds the cursor shape that will appear whenever
* the mouse is hovering over the label.
*
* default: @c Qt.IBeamCursor
*
* @property Qt::CursorShape cursorShape
*/
property alias cursorShape: hoverHandler.cursorShape
activeFocusOnTab: root.activeFocusOnTab
color: Kirigami.Theme.textColor
readOnly: true
selectByMouse: true
padding: 0
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
textFormat: TextEdit.AutoText
verticalAlignment: TextEdit.AlignTop
wrapMode: TextEdit.WordWrap
font: root.font
persistentSelection: contextMenu.visible
onLinkActivated: root.linkActivated(textEdit.hoveredLink)
onLinkHovered: root.linkHovered(textEdit.hoveredLink)
onEditingFinished: root.editingFinished()
HoverHandler {
id: hoverHandler
// By default HoverHandler accepts the left button while it shouldn't accept anything,
// causing https://bugreports.qt.io/browse/QTBUG-106489.
// Qt.NoButton unfortunately is not a valid value for acceptedButtons.
// Disabling masks the problem, but
// there is no proper workaround other than an upstream fix
// See qqc2-desktop-style Label.qml
enabled: false
cursorShape: root.cursorShape ? root.cursorShape : (textEdit.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor)
}
TapHandler {
// For custom click actions we want selection to be turned off
enabled: !textEdit.selectByMouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.LeftButton
onTapped: root.clicked()
}
TapHandler {
enabled: textEdit.selectByMouse && root.contextMenuEnabled
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton
onPressedChanged: if (pressed) {
contextMenu.popup();
}
}
QQC2.Menu {
id: contextMenu
QQC2.MenuItem {
action: T.Action {
icon.name: "edit-copy-symbolic"
text: qsTr("Copy")
shortcut: StandardKey.Copy
}
enabled: textEdit.selectedText.length > 0
onTriggered: {
textEdit.copy();
textEdit.deselect();
}
}
QQC2.MenuSeparator {}
QQC2.MenuItem {
action: T.Action {
icon.name: "edit-select-all-symbolic"
text: qsTr("Select All")
shortcut: StandardKey.SelectAll
}
onTriggered: {
textEdit.selectAll();
}
}
}
}
}
@@ -0,0 +1,541 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private"
/**
* An item delegate intended to support extra actions obtainable
* by uncovering them by dragging away the item with the handle.
*
* This acts as a container for normal list items.
*
* Example usage:
* @code
* ListView {
* model: myModel
* delegate: SwipeListItem {
* QQC2.Label {
* text: model.text
* }
* actions: [
* Action {
* icon.name: "document-decrypt"
* onTriggered: print("Action 1 clicked")
* },
* Action {
* icon.name: model.action2Icon
* onTriggered: //do something
* }
* ]
* }
*
* }
* @endcode
*
* @inherit QtQuick.Templates.SwipeDelegate
*/
QQC2.SwipeDelegate {
id: listItem
//BEGIN properties
/**
* @brief This property sets whether the item should emit signals related to mouse interaction.
*
* default: ``true``
*
* @deprecated Use hoverEnabled instead.
* @property bool supportsMouseEvents
*/
property alias supportsMouseEvents: listItem.hoverEnabled
/**
* @brief This property tells whether the cursor is currently hovering over the item.
*
* On mobile touch devices, this will be true only when pressed.
*
* @see QtQuick.Templates.ItemDelegate::hovered
* @deprecated This will be removed in KF6; use the ``hovered`` property instead.
* @property bool containsMouse
*/
readonly property alias containsMouse: listItem.hovered
/**
* @brief This property sets whether instances of this list item will alternate
* between two colors, helping readability.
*
* It is suggested to use this only when implementing a view with multiple columns.
*
* default: ``false``
*
* @since 2.7
*/
property bool alternatingBackground: false
/**
* @brief This property sets whether this item is a section delegate.
*
* Setting this to true will make the list item look like a "title" for items under it.
*
* default: ``false``
*
* @see ListSectionHeader
*/
property bool sectionDelegate: false
/**
* @brief This property sets whether the separator is visible.
*
* The separator is a line between this and the item under it.
*
* default: ``false``
*/
property bool separatorVisible: false
/**
* @brief This property holds the background color of the list item.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.backgroundColor``
*/
property color backgroundColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the background color to be used when
* background alternating is enabled.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.alternateBackgroundColor``
*
* @since 2.7
*/
property color alternateBackgroundColor: Kirigami.Theme.alternateBackgroundColor
/**
* @brief This property holds the color of the background
* when the item is pressed or selected.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.highlightColor``
*/
property color activeBackgroundColor: Kirigami.Theme.highlightColor
/**
* @brief This property holds the color of the text in the item.
*
* It is advised to use the default value.
* default: ``Theme.textColor``
*
* If custom text elements are inserted in a SwipeListItem,
* their color will have to be manually set with this property.
*/
property color textColor: Kirigami.Theme.textColor
/**
* @brief This property holds the color of the text when the item is pressed or selected.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.highlightedTextColor``
*
* If custom text elements are inserted in a SwipeListItem,
* their color property will have to be manually bound with this property
*/
property color activeTextColor: Kirigami.Theme.highlightedTextColor
/**
* @brief This property tells whether actions are visible and interactive.
*
* True if it's possible to see and interact with the item's actions.
*
* Actions become hidden while editing of an item, for example.
*
* @since 2.5
*/
readonly property bool actionsVisible: actionsLayout.hasVisibleActions
/**
* @brief This property sets whether actions behind this SwipeListItem will always be visible.
*
* default: `true in desktop and tablet mode`
*
* @since 2.15
*/
property bool alwaysVisibleActions: !Kirigami.Settings.isMobile
/**
* @brief This property holds actions of the list item.
*
* At most 4 actions can be revealed when sliding away the list item;
* others will be shown in the overflow menu.
*/
property list<T.Action> actions
/**
* @brief This property holds the width of the overlay.
*
* The value can represent the width of the handle component or the action layout.
*
* @since 2.19
* @property real overlayWidth
*/
readonly property alias overlayWidth: overlayLoader.width
//END properties
LayoutMirroring.childrenInherit: true
hoverEnabled: true
implicitWidth: contentItem ? implicitContentWidth : Kirigami.Units.gridUnit * 12
width: parent ? parent.width : implicitWidth
implicitHeight: Math.max(Kirigami.Units.gridUnit * 2, implicitContentHeight) + topPadding + bottomPadding
padding: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
leftPadding: padding * 2 + (mirrored ? overlayLoader.paddingOffset : 0)
rightPadding: padding * 2 + (mirrored ? 0 : overlayLoader.paddingOffset)
topPadding: padding
bottomPadding: padding
QtObject {
id: internal
property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || (listItem.parent instanceof Flickable ? listItem.parent : null)) : null)
function viewHasPropertySwipeFilter(): bool {
return view && view.parent && view.parent.parent && "_swipeFilter" in view.parent.parent;
}
readonly property QtObject swipeFilterItem: (viewHasPropertySwipeFilter() && view.parent.parent._swipeFilter) ? view.parent.parent._swipeFilter : null
readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false
// install the SwipeItemEventFilter
onViewChanged: {
if (listItem.alwaysVisibleActions || !Kirigami.Settings.tabletMode) {
return;
}
if (viewHasPropertySwipeFilter() && Kirigami.Settings.tabletMode && !internal.view.parent.parent._swipeFilter) {
const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
internal.view.parent.parent._swipeFilter = component.createObject(internal.view.parent.parent);
component.destroy();
}
}
}
Connections {
target: Kirigami.Settings
function onTabletModeChanged() {
if (!internal.viewHasPropertySwipeFilter()) {
return;
}
if (Kirigami.Settings.tabletMode) {
if (!internal.swipeFilterItem) {
const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent);
component.destroy();
}
} else {
if (listItem.ListView.view.parent.parent._swipeFilter) {
listItem.ListView.view.parent.parent._swipeFilter.destroy();
slideAnim.to = 0;
slideAnim.restart();
}
}
}
}
//BEGIN items
Loader {
id: overlayLoader
readonly property int paddingOffset: (visible ? width : 0) + Kirigami.Units.smallSpacing
readonly property var theAlias: anchors
function validate(want, defaultValue) {
const expectedLeftPadding = () => listItem.padding * 2 + (listItem.mirrored ? overlayLoader.paddingOffset : 0)
const expectedRightPadding = () => listItem.padding * 2 + (listItem.mirrored ? 0 : overlayLoader.paddingOffset)
const warningText =
`Don't override the leftPadding or rightPadding on a SwipeListItem!\n` +
`This makes it impossible for me to adjust my layout as I need to for various usecases.\n` +
`I'll try to fix the mistake for you, but you should remove your overrides from your app's code entirely.\n` +
`If I can't fix the paddings, I'll fall back to a default layout, but it'll be slightly incorrect and lacks\n` +
`adaptations needed for touch screens and right-to-left languages, among other things.`
if (listItem.leftPadding != expectedLeftPadding() || listItem.rightPadding != expectedRightPadding()) {
listItem.leftPadding = Qt.binding(expectedLeftPadding)
listItem.rightPadding = Qt.binding(expectedRightPadding)
console.warn(warningText)
return defaultValue
}
return want
}
anchors {
right: validate(listItem.mirrored ? undefined : (contentItem ? contentItem.right : undefined), contentItem ? contentItem.right : undefined)
rightMargin: validate(-paddingOffset, 0)
left: validate(!listItem.mirrored ? undefined : (contentItem ? contentItem.left : undefined), undefined)
leftMargin: validate(-paddingOffset, 0)
top: parent.top
bottom: parent.bottom
}
LayoutMirroring.enabled: false
parent: listItem
z: contentItem ? contentItem.z + 1 : 0
width: item ? item.implicitWidth : actionsLayout.implicitWidth
active: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
visible: listItem.actionsVisible && opacity > 0
asynchronous: true
sourceComponent: handleComponent
opacity: listItem.alwaysVisibleActions || Kirigami.Settings.tabletMode || listItem.hovered ? 1 : 0
Behavior on opacity {
OpacityAnimator {
id: opacityAnim
duration: Kirigami.Units.veryShortDuration
easing.type: Easing.InOutQuad
}
}
}
Component {
id: handleComponent
MouseArea {
id: dragButton
anchors {
right: parent.right
}
implicitWidth: Kirigami.Units.iconSizes.smallMedium
preventStealing: true
readonly property real openPosition: (listItem.width - width - listItem.leftPadding * 2)/listItem.width
property real startX: 0
property real lastPosition: 0
property bool openIntention
onPressed: mouse => {
startX = mapToItem(listItem, 0, 0).x;
}
onClicked: mouse => {
if (Math.abs(mapToItem(listItem, 0, 0).x - startX) > Qt.styleHints.startDragDistance) {
return;
}
if (listItem.mirrored) {
if (listItem.swipe.position < 0.5) {
slideAnim.to = openPosition
} else {
slideAnim.to = 0
}
} else {
if (listItem.swipe.position > -0.5) {
slideAnim.to = -openPosition
} else {
slideAnim.to = 0
}
}
slideAnim.restart();
}
onPositionChanged: mouse => {
const pos = mapToItem(listItem, mouse.x, mouse.y);
if (listItem.mirrored) {
listItem.swipe.position = Math.max(0, Math.min(openPosition, (pos.x / listItem.width)));
openIntention = listItem.swipe.position > lastPosition;
} else {
listItem.swipe.position = Math.min(0, Math.max(-openPosition, (pos.x / (listItem.width -listItem.rightPadding) - 1)));
openIntention = listItem.swipe.position < lastPosition;
}
lastPosition = listItem.swipe.position;
}
onReleased: mouse => {
if (listItem.mirrored) {
if (openIntention) {
slideAnim.to = openPosition
} else {
slideAnim.to = 0
}
} else {
if (openIntention) {
slideAnim.to = -openPosition
} else {
slideAnim.to = 0
}
}
slideAnim.restart();
}
Kirigami.Icon {
id: handleIcon
anchors.fill: parent
selected: listItem.checked || (listItem.down && !listItem.checked && !listItem.sectionDelegate)
source: (listItem.mirrored ? (listItem.background.x < listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left") : (listItem.background.x < -listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left"))
}
Connections {
id: swipeFilterConnection
target: internal.edgeEnabled ? internal.swipeFilterItem : null
function onPeekChanged() {
if (!listItem.actionsVisible) {
return;
}
if (listItem.mirrored) {
listItem.swipe.position = Math.max(0, Math.min(dragButton.openPosition, internal.swipeFilterItem.peek));
dragButton.openIntention = listItem.swipe.position > dragButton.lastPosition;
} else {
listItem.swipe.position = Math.min(0, Math.max(-dragButton.openPosition, -internal.swipeFilterItem.peek));
dragButton.openIntention = listItem.swipe.position < dragButton.lastPosition;
}
dragButton.lastPosition = listItem.swipe.position;
}
function onPressed(mouse) {
if (internal.edgeEnabled) {
dragButton.pressed(mouse);
}
}
function onClicked(mouse) {
if (Math.abs(listItem.background.x) < Kirigami.Units.gridUnit && internal.edgeEnabled) {
dragButton.clicked(mouse);
}
}
function onReleased(mouse) {
if (internal.edgeEnabled) {
dragButton.released(mouse);
}
}
function onCurrentItemChanged() {
if (!internal.edgeEnabled) {
slideAnim.to = 0;
slideAnim.restart();
}
}
}
}
}
// TODO: expose in API?
Component {
id: actionsBackgroundDelegate
Item {
anchors.fill: parent
z: 1
readonly property Item contentItem: swipeBackground
Rectangle {
id: swipeBackground
anchors {
top: parent.top
bottom: parent.bottom
}
clip: true
color: parent.pressed ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Qt.darker(Kirigami.Theme.backgroundColor, 1.05)
x: listItem.mirrored ? listItem.background.x - width : (listItem.background.x + listItem.background.width)
width: listItem.mirrored ? parent.width - (parent.width - x) : parent.width - x
TapHandler {
onTapped: listItem.swipe.close()
}
EdgeShadow {
edge: Qt.TopEdge
visible: background.x != 0
anchors {
right: parent.right
left: parent.left
top: parent.top
}
}
EdgeShadow {
edge: listItem.mirrored ? Qt.RightEdge : Qt.LeftEdge
visible: background.x != 0
anchors {
top: parent.top
bottom: parent.bottom
}
}
}
visible: listItem.swipe.position != 0
}
}
RowLayout {
id: actionsLayout
LayoutMirroring.enabled: listItem.mirrored
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
rightMargin: Kirigami.Units.smallSpacing
}
visible: parent !== listItem
parent: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
? listItem.swipe.leftItem?.contentItem || listItem.swipe.rightItem?.contentItem || listItem
: overlayLoader
property bool hasVisibleActions: false
function updateVisibleActions(definitelyVisible: bool) {
hasVisibleActions = definitelyVisible || listItem.actions.some(isActionVisible);
}
function isActionVisible(action: T.Action): bool {
return (action instanceof Kirigami.Action) ? action.visible : true;
}
Repeater {
model: listItem.actions
delegate: QQC2.ToolButton {
required property T.Action modelData
action: modelData
display: T.AbstractButton.IconOnly
visible: actionsLayout.isActionVisible(action)
onVisibleChanged: actionsLayout.updateVisibleActions(visible);
Component.onCompleted: actionsLayout.updateVisibleActions(visible);
Component.onDestruction: actionsLayout.updateVisibleActions(visible);
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && QQC2.ToolTip.text.length > 0
QQC2.ToolTip.text: (action as Kirigami.Action)?.tooltip ?? action?.text ?? ""
onClicked: {
slideAnim.to = 0;
slideAnim.restart();
}
Accessible.name: text
Accessible.description: (action as Kirigami.Action)?.tooltip ?? ""
}
}
}
swipe {
enabled: false
right: listItem.alwaysVisibleActions || listItem.mirrored || !Kirigami.Settings.tabletMode ? null : actionsBackgroundDelegate
left: listItem.alwaysVisibleActions || listItem.mirrored && Kirigami.Settings.tabletMode ? actionsBackgroundDelegate : null
}
NumberAnimation {
id: slideAnim
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
target: listItem.swipe
property: "position"
from: listItem.swipe.position
}
//END items
}
@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigami.private as KirigamiPrivate
import QtQuick.Controls as QQC2
/**
* @brief A link button that contains a URL.
*
* It will open the url by default, allow to copy it if triggered with the
* secondary mouse button.
*
* @since 5.63
* @since org.kde.kirigami 2.6
* @inherit QtQuick.LinkButton
*/
Kirigami.LinkButton {
id: button
property string url
text: url
enabled: url.length > 0
visible: text.length > 0
acceptedButtons: Qt.LeftButton | Qt.RightButton
Accessible.name: text
Accessible.description: text !== url
? qsTr("Open link %1", "@info:whatsthis").arg(url)
: qsTr("Open link", "@info:whatsthis")
onPressed: mouse => {
if (mouse.button === Qt.RightButton) {
menu.popup();
}
}
onClicked: mouse => {
if (mouse.button !== Qt.RightButton) {
Qt.openUrlExternally(url);
}
}
QQC2.ToolTip {
// If button's text has been overridden, show a tooltip to expose the raw URL
visible: button.text !== button.url && button.mouseArea.containsMouse
text: button.url
}
QQC2.Menu {
id: menu
QQC2.MenuItem {
text: qsTr("Copy Link to Clipboard")
icon.name: "edit-copy"
onClicked: KirigamiPrivate.CopyHelperPrivate.copyTextToClipboard(button.url)
}
}
}
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQml
import QtQuick
QtObject {
property string name
property string source
property int width
property int height
property color color: Qt.rgba(0, 0, 0, 0)
}
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.MenuItem {
visible: !(action instanceof Kirigami.Action) || action.visible
autoExclusive: action instanceof Kirigami.Action && action.autoExclusive
height: visible ? implicitHeight : 0
QQC2.ToolTip.text: (action instanceof Kirigami.Action) ? action.tooltip : ""
QQC2.ToolTip.visible: hovered && QQC2.ToolTip.text.length > 0
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Accessible.onPressAction: action.trigger()
}
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.Menu {
id: root
property alias actions: actionsInstantiator.model
property Component submenuComponent
property Component itemDelegate: ActionMenuItem {}
property Component separatorDelegate: QQC2.MenuSeparator {
property T.Action action
}
property Component loaderDelegate: Loader {
property T.Action action
}
property T.Action parentAction
property T.MenuItem parentItem
Instantiator {
id: actionsInstantiator
active: root.visible
delegate: QtObject {
readonly property T.Action action: modelData
property QtObject item: null
property bool isSubMenu: false
Component.onCompleted: {
const isKirigamiAction = action instanceof Kirigami.Action;
if (!isKirigamiAction || action.children.length === 0) {
if (isKirigamiAction && action.separator) {
item = root.separatorDelegate.createObject(null, { action });
} else if (action.displayComponent) {
item = root.loaderDelegate.createObject(null, {
action,
sourceComponent: action.displayComponent,
});
} else {
item = root.itemDelegate.createObject(null, { action });
}
root.addItem(item)
} else if (root.submenuComponent) {
item = root.submenuComponent.createObject(null, {
parentAction: action,
title: action.text,
actions: action.children,
});
root.insertMenu(root.count, item);
item.parentItem = root.contentData[root.contentData.length - 1];
item.parentItem.icon = action.icon;
isSubMenu = true;
}
}
Component.onDestruction: {
if (isSubMenu) {
root.removeMenu(item);
} else {
root.removeItem(item);
}
item.destroy();
}
}
}
}
@@ -0,0 +1,224 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* This Component is used as the header of GlobalDrawer and as the header
* of Card, It can be accessed there as a grouped property but can never
* be instantiated directly.
* \private
*/
Kirigami.ShadowedImage {
id: root
//BEGIN properties
/**
* @brief This property holds an icon to be displayed alongside the title.
*
* It can be a QIcon, a FreeDesktop-compatible icon name, or any URL understood by QtQuick.Image.
*
* @property var titleIcon
*/
property alias titleIcon: headingIcon.source
/**
* @brief This property holds the title's text which is to be displayed on top.
* of the image.
* @see QtQuick.Text::text
* @property string title
*/
property alias title: heading.text
/**
* @brief This property holds the title's position.
*
* default: ``Qt.AlignTop | Qt.AlignLeft``
*
* @property Qt::Alignment titleAlignment
*/
property int titleAlignment: Qt.AlignTop | Qt.AlignLeft
/**
* @brief This property holds the title's level.
*
* Available text size values range from 1 (largest) to 5 (smallest).
*
* default: ``1``
*
* @see org::kde::kirigami::Heading::level
* @property int titleLevel
*/
property alias titleLevel: heading.level
/**
* @brief This property holds the title's wrap mode.
*
* default: ``Text.NoWrap``
*
* @see QtQuick.Text::wrapMode
* @property int titleWrapMode
*/
property alias titleWrapMode: heading.wrapMode
/**
* @brief This property holds whether the title is part of an item considered
* checkable.
*
* If true, a checkbox will appear in the top-right corner of the title area.
*
* default: false
*
* @property bool checkable
*/
property bool checkable: false
/**
* @brief This property holds whether the title's checkbox is currently checked.
*
* If using this outside of a GlobalDrawer or a Card, you must manually bind
* this to the checked condition of the parent item, or whatever else in your
* UI signals checkability. You must also handle the `toggled` signal when
* the user manually clicks the checkbox.
*
* default: false
*
* @property bool checked
*/
property bool checked: false
property int leftPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int topPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int rightPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int bottomPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int implicitWidth: Layout.preferredWidth
readonly property bool empty: title.length === 0 && // string
source.toString().length === 0 && // QUrl
!titleIcon // QVariant hanled by Kirigami.Icon
//END properties
signal toggled(bool checked)
Layout.fillWidth: true
Layout.preferredWidth: titleLayout.implicitWidth || sourceSize.width
Layout.preferredHeight: titleLayout.completed && source.toString().length > 0 ? width/(sourceSize.width / sourceSize.height) : Layout.minimumHeight
Layout.minimumHeight: titleLayout.implicitHeight > 0 ? titleLayout.implicitHeight + Kirigami.Units.smallSpacing * 2 : 0
onTitleAlignmentChanged: {
// VERTICAL ALIGNMENT
titleLayout.anchors.top = undefined
titleLayout.anchors.verticalCenter = undefined
titleLayout.anchors.bottom = undefined
shadowedRectangle.anchors.top = undefined
shadowedRectangle.anchors.verticalCenter = undefined
shadowedRectangle.anchors.bottom = undefined
if (root.titleAlignment & Qt.AlignTop) {
titleLayout.anchors.top = root.top
shadowedRectangle.anchors.top = root.top
}
else if (root.titleAlignment & Qt.AlignVCenter) {
titleLayout.anchors.verticalCenter = root.verticalCenter
shadowedRectangle.anchors.verticalCenter = root.verticalCenter
}
else if (root.titleAlignment & Qt.AlignBottom) {
titleLayout.anchors.bottom = root.bottom
shadowedRectangle.anchors.bottom = root.bottom
}
// HORIZONTAL ALIGNMENT
titleLayout.anchors.left = undefined
titleLayout.anchors.horizontalCenter = undefined
titleLayout.anchors.right = undefined
if (root.titleAlignment & Qt.AlignRight) {
titleLayout.anchors.right = root.right
}
else if (root.titleAlignment & Qt.AlignHCenter) {
titleLayout.anchors.horizontalCenter = root.horizontalCenter
}
else if (root.titleAlignment & Qt.AlignLeft) {
titleLayout.anchors.left = root.left
}
}
fillMode: Image.PreserveAspectCrop
asynchronous: true
color: "transparent"
Component.onCompleted: {
titleLayout.completed = true;
}
Kirigami.ShadowedRectangle {
id: shadowedRectangle
anchors {
left: parent.left
right: parent.right
}
height: Math.min(parent.height, titleLayout.height * 1.5)
opacity: 0.5
color: "black"
corners.topLeftRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topLeftRadius : 0
corners.topRightRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topRightRadius : 0
corners.bottomLeftRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomLeftRadius : 0
corners.bottomRightRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomRightRadius : 0
visible: root.source.toString().length !== 0 && root.title.length !== 0 && ((root.titleAlignment & Qt.AlignTop) || (root.titleAlignment & Qt.AlignVCenter) || (root.titleAlignment & Qt.AlignBottom))
}
RowLayout {
id: titleLayout
property bool completed: false
anchors {
leftMargin: root.leftPadding
topMargin: root.topPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
width: Math.min(implicitWidth, parent.width -root.leftPadding -root.rightPadding - (checkboxLoader.active ? Kirigami.Units.largeSpacing : 0))
height: Math.min(implicitHeight, parent.height -root.topPadding -root.bottomPadding)
Kirigami.Icon {
id: headingIcon
Layout.minimumWidth: Kirigami.Units.iconSizes.large
Layout.minimumHeight: width
visible: valid
isMask: false
}
Kirigami.Heading {
id: heading
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter
visible: text.length > 0
level: 1
color: root.source.toString().length > 0 ? "white" : Kirigami.Theme.textColor
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
}
Loader {
id: checkboxLoader
anchors {
top: parent.top
right: parent.right
topMargin: root.topPadding
}
active: root.checkable
sourceComponent: QQC2.CheckBox {
checked: root.checked
onToggled: root.toggled(checked);
}
}
}
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
QQC2.ItemDelegate {
id: listItem
required property T.Action tAction
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool isSeparator: kAction?.separator ?? false
readonly property bool isExpandable: kAction?.expandible ?? false
checked: tAction.checked || (actionsMenu && actionsMenu.visible)
highlighted: checked
icon.name: tAction.icon.name
text: tAction.text ? tAction.text : tAction.tooltip
hoverEnabled: (!isExpandable || root.collapsed) && !Kirigami.Settings.tabletMode && !isSeparator
font.pointSize: Kirigami.Theme.defaultFont.pointSize * (isExpandable ? 1.30 : 1)
enabled: !isExpandable && tAction.enabled
visible: kAction?.visible ?? true
opacity: enabled || isExpandable ? 1.0 : 0.6
Accessible.onPressAction: listItem.clicked()
Kirigami.Separator {
id: separatorAction
visible: listItem.isSeparator
Layout.fillWidth: true
}
ActionsMenu {
id: actionsMenu
y: Kirigami.Settings.isMobile ? -height : listItem.height
actions: kAction?.children ?? []
submenuComponent: ActionsMenu {}
}
Loader {
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent: kAction?.displayComponent ?? null
onStatusChanged: {
for (const child of parent.children) {
if (child === this) {
child.visible = status === Loader.Ready;
break;
} else {
child.visible = status !== Loader.Ready;
}
}
}
Component.onCompleted: statusChanged()
}
onPressed: {
if (kAction && kAction.children.length > 0) {
actionsMenu.open();
}
}
onClicked: {
if (!kAction || kAction.children.length === 0) {
root.drawerOpen = false;
}
tAction?.trigger();
}
}
@@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is the default background for Cards.
*
* It provides background feedback on hover and click events, border customizability, and the ability to change the radius of each individual corner.
*
* @inherit org::kde::kirigami::ShadowedRectangle
*/
Kirigami.ShadowedRectangle {
id: root
//BEGIN properties
/**
* @brief This property sets whether there should be a background change on a click event.
*
* default: ``false``
*/
property bool clickFeedback: false
/**
* @brief This property sets whether there should be a background change on a click event.
*
* default: ``false``
*/
property bool hoverFeedback: false
/**
* @brief This property holds the card's normal background color.
*
* default: ``Kirigami.Theme.backgroundColor``
*/
property color defaultColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the color displayed when a click event is triggered.
* @see DefaultCardBackground::clickFeedback
*/
property color pressedColor: Kirigami.ColorUtils.tintWithAlpha(
defaultColor,
Kirigami.Theme.highlightColor, 0.3)
/**
* @brief This property holds the color displayed when a hover event is triggered.
* @see DefaultCardBackground::hoverFeedback
*/
property color hoverColor: Kirigami.ColorUtils.tintWithAlpha(
defaultColor,
Kirigami.Theme.highlightColor, 0.1)
/**
* @brief This property holds the border width which is displayed at the edge of DefaultCardBackground.
*
* default: ``1``
*/
property int borderWidth: 1
/**
* @brief This property holds the border color which is displayed at the edge of DefaultCardBackground.
*/
property color borderColor: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
//END properties
color: {
if (root.parent.checked || (root.clickFeedback && (root.parent.down || root.parent.highlighted)))
return root.pressedColor
else if (root.hoverFeedback && root.parent.hovered)
return root.hoverColor
return root.defaultColor
}
radius: Kirigami.Units.cornerRadius
border {
width: root.borderWidth
color: root.borderColor
}
shadow {
size: Kirigami.Units.gridUnit
color: Qt.rgba(0, 0, 0, 0.05)
yOffset: 2
}
// basic drop shadow
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Math.round(Kirigami.Units.smallSpacing / 4)
radius: root.radius
height: root.height
color: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.6), 1.1)
visible: !root.clickFeedback || !root.parent.down
z: -1
}
}
@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import org.kde.kirigami as Kirigami
Rectangle {
/**
* @brief This property holds the chip's default background color.
*/
property color defaultColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the color of the Chip's background when it is being pressed.
* @see QtQuick.AbstractButton::down
*/
property color pressedColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.3)
/**
* @brief This property holds the color of the Chip's background when it is checked.
* @see QtQuick.AbstractButton::checked
*/
property color checkedColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.2)
/**
* @brief This property holds the chip's default border color.
*/
property color defaultBorderColor: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
/**
* @brief This property holds the color of the Chip's border when it is checked.
* @see QtQuick.AbstractButton::checked
*/
property color checkedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.9)
/**
* @brief This property holds the color of the Chip's border when it is being pressed.
* @see QtQuick.AbstractButton::down
*/
property color pressedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.7)
/**
* @brief This property holds the color of the Chip's border when it is hovered.
* @see QtQuick.Control::hovered
*/
property color hoveredBorderColor: Kirigami.Theme.hoverColor
Kirigami.Theme.colorSet: Kirigami.Theme.Header
Kirigami.Theme.inherit: false
color: {
if (parent.down) {
return pressedColor
} else if (parent.checked) {
return checkedColor
} else {
return defaultColor
}
}
border.color: {
if (parent.down) {
return pressedBorderColor
} else if (parent.checked) {
return checkedBorderColor
} else if (parent.hovered) {
return hoveredBorderColor
} else {
return defaultBorderColor
}
}
border.width: 1
radius: Kirigami.Units.cornerRadius
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* This component is used as a default representation for a page title within
* page's header/toolbar. It is just a Heading item with shrinking + eliding
* behavior.
*
* \private
*/
Item {
property alias text: heading.text
Layout.fillWidth: true
Layout.minimumWidth: 0
Layout.maximumWidth: implicitWidth
implicitWidth: Math.ceil(heading.implicitWidth)
implicitHeight: Math.ceil(heading.implicitHeight)
Kirigami.Heading {
id: heading
anchors.fill: parent
maximumLineCount: 1
elide: Text.ElideRight
textFormat: Text.PlainText
}
}
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: shadow
/**
* @brief This property holds the edge of the shadow that will determine the direction of the gradient.
* The acceptable values are:
* * ``Qt.TopEdge``: the top edge of the content item.
* * ``Qt.LeftEdge``: the left edge of the content item
* * ``Qt.RightEdge``: the right edge of the content item.
* * ``Qt.BottomEdge``: the bottom edge of the content item.
*
* @see Qt::Edges
*/
property int edge: Qt.LeftEdge
property int radius: Kirigami.Units.cornerRadius
implicitWidth: radius
implicitHeight: radius
Rectangle {
x: shadow.width / 2 - width / 2
y: shadow.height / 2 - height / 2
width: (shadow.edge === Qt.LeftEdge || shadow.edge === Qt.RightEdge) ? shadow.height : shadow.width
height: (shadow.edge === Qt.LeftEdge || shadow.edge === Qt.RightEdge) ? shadow.width : shadow.height
rotation: {
switch (shadow.edge) {
case Qt.TopEdge: return 0;
case Qt.LeftEdge: return 270;
case Qt.RightEdge: return 90;
case Qt.BottomEdge: return 180;
}
}
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(0, 0, 0, 0.25)
}
GradientStop {
position: 0.20
color: Qt.rgba(0, 0, 0, 0.1)
}
GradientStop {
position: 0.35
color: Qt.rgba(0, 0, 0, 0.02)
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
}
@@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Controls.impl as QQC2Impl
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.ItemDelegate {
id: listItem
required property T.Action tAction
// `as` case operator is still buggy
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool actionVisible: kAction?.visible ?? true
readonly property bool isSeparator: kAction?.separator ?? false
readonly property bool isExpandable: kAction?.expandible ?? false
readonly property bool hasChildren: kAction ? kAction.children.length > 0 : false
readonly property bool hasVisibleMenu: actionsMenu?.visible ?? false
readonly property bool hasToolTip: kAction ? kAction.tooltip !== "" : false
checked: checkedBinding()
highlighted: checked
activeFocusOnTab: true
width: parent.width
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
id: iconItem
color: listItem.tAction.icon.color
source: listItem.tAction.icon.name || listItem.tAction.icon.source
readonly property int size: Kirigami.Units.iconSizes.smallMedium
Layout.minimumHeight: size
Layout.maximumHeight: size
Layout.minimumWidth: size
Layout.maximumWidth: size
selected: (listItem.highlighted || listItem.checked || listItem.down)
visible: source !== undefined && !listItem.isSeparator
}
QQC2Impl.MnemonicLabel {
id: labelItem
visible: !listItem.isSeparator
text: width > height * 2 ? listItem.Kirigami.MnemonicData.mnemonicLabel : ""
// Work around Qt bug where left aligned text is not right aligned
// in RTL mode unless horizontalAlignment is explicitly set.
// https://bugreports.qt.io/browse/QTBUG-95873
horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true
mnemonicVisible: listItem.Kirigami.MnemonicData.active
color: (listItem.highlighted || listItem.checked || listItem.down) ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
elide: Text.ElideRight
font: listItem.font
opacity: {
if (root.collapsed) {
return 0;
} else if (!listItem.enabled) {
return 0.6;
} else {
return 1.0;
}
}
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration/2
easing.type: Easing.InOutQuad
}
}
}
Kirigami.Separator {
id: separatorAction
visible: listItem.isSeparator
Layout.fillWidth: true
}
Kirigami.Icon {
isMask: true
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: !root.collapsed ? 0 : -width
Layout.preferredHeight: !root.collapsed ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.small/2
opacity: 0.7
selected: listItem.checked || listItem.down
Layout.preferredWidth: Layout.preferredHeight
source: listItem.mirrored ? "go-next-symbolic-rtl" : "go-next-symbolic"
visible: (!listItem.isExpandable || root.collapsed) && !listItem.isSeparator && listItem.hasChildren
}
}
Accessible.name: listItem.tAction?.text ?? ""
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: tAction?.text ?? ""
Shortcut {
sequence: listItem.Kirigami.MnemonicData.sequence
onActivated: listItem.clicked()
}
property ActionsMenu actionsMenu: ActionsMenu {
x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
actions: listItem.kAction?.children ?? []
submenuComponent: ActionsMenu {}
onVisibleChanged: {
if (visible) {
stackView.openSubMenu = listItem.actionsMenu;
} else if (stackView.openSubMenu === listItem.actionsMenu) {
stackView.openSubMenu = null;
}
}
}
// TODO: animate the hide by collapse
visible: actionVisible && opacity > 0
opacity: !root.collapsed || iconItem.source.toString().length > 0
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration / 2
easing.type: Easing.InOutQuad
}
}
enabled: tAction?.enabled ?? false
hoverEnabled: (!isExpandable || root.collapsed) && !Kirigami.Settings.tabletMode && !isSeparator
font.pointSize: isExpandable ? Kirigami.Theme.defaultFont.pointSize * 1.30 : Kirigami.Theme.defaultFont.pointSize
height: implicitHeight * opacity
QQC2.ToolTip {
visible: !listItem.isSeparator
&& (listItem.hasToolTip || root.collapsed)
&& !listItem.hasVisibleMenu
&& listItem.hovered
&& text.length > 0
text: (listItem.kAction?.tooltip || listItem.tAction?.text) ?? ""
delay: Kirigami.Units.toolTipDelay
y: (listItem.height - height) / 2
x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
}
onHoveredChanged: {
if (!hovered) {
return;
}
if (stackView.openSubMenu) {
stackView.openSubMenu.visible = false;
if (actionsMenu.count > 0) {
actionsMenu.popup(this, width, 0);
}
}
}
onClicked: trigger()
Accessible.onPressAction: trigger()
Keys.onEnterPressed: event => trigger()
Keys.onReturnPressed: event => trigger()
function trigger() {
tAction?.trigger();
if (hasChildren) {
if (root.collapsed) {
if (actionsMenu.count > 0 && !actionsMenu.visible) {
stackView.openSubMenu = actionsMenu;
actionsMenu.popup(this, width, 0);
}
} else {
stackView.push(menuComponent, {
model: kAction?.children ?? [],
level: level + 1,
current: tAction,
});
}
} else if (root.resetMenuOnTriggered) {
root.resetMenu();
}
checked = Qt.binding(() => checkedBinding());
}
function checkedBinding(): bool {
return (tAction?.checked || actionsMenu?.visible) ?? false;
}
Keys.onDownPressed: event => nextItemInFocusChain().focus = true
Keys.onUpPressed: event => nextItemInFocusChain(false).focus = true
}
@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
Kirigami.Dialog {
id: dialog
clip: true
modal: true
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
header: Kirigami.AbstractApplicationHeader {
pageRow: null
page: null
minimumHeight: Kirigami.Units.gridUnit * 1.6
maximumHeight: Kirigami.Units.gridUnit * 1.6
preferredHeight: Kirigami.Units.gridUnit * 1.6
Keys.onEscapePressed: event => {
if (dialog.opened) {
dialog.close();
} else {
event.accepted = false;
}
}
contentItem: RowLayout {
width: parent.width
Kirigami.Heading {
Layout.leftMargin: Kirigami.Units.largeSpacing
text: dialog.title
elide: Text.ElideRight
}
Item {
Layout.fillWidth: true
}
Kirigami.Icon {
id: closeIcon
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
active: closeMouseArea.containsMouse
MouseArea {
id: closeMouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: mouse => dialog.close();
}
}
}
}
contentItem: QQC2.Control {
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
}
}
@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: control
signal menuAboutToShow()
hoverEnabled: true
display: QQC2.ToolButton.TextBesideIcon
property bool showMenuArrow: !Kirigami.DisplayHint.displayHintSet(action, Kirigami.DisplayHint.HideChildIndicator)
property list<T.Action> menuActions: {
if (action instanceof Kirigami.Action) {
return action.children;
}
return []
}
property Component menuComponent: ActionsMenu {
submenuComponent: ActionsMenu { }
}
property T.Menu menu: null
// We create the menu instance only when there are any actual menu items.
// This also happens in the background, avoiding slowdowns due to menu item
// creation on the main thread.
onMenuActionsChanged: {
if (menuComponent && menuActions.length > 0) {
if (!menu) {
const setupIncubatedMenu = incubatedMenu => {
menu = incubatedMenu
// Important: We handle the press on parent in the parent, so ignore it here.
menu.closePolicy = QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
menu.closed.connect(() => control.checked = false)
menu.actions = control.menuActions
}
const incubator = menuComponent.incubateObject(control, { actions: menuActions })
if (incubator.status !== Component.Ready) {
incubator.onStatusChanged = status => {
if (status === Component.Ready) {
setupIncubatedMenu(incubator.object)
}
}
} else {
setupIncubatedMenu(incubator.object);
}
} else {
menu.actions = menuActions
}
}
}
visible: action instanceof Kirigami.Action ? action.visible : true
autoExclusive: action instanceof Kirigami.Action ? action.autoExclusive : false
// Workaround for QTBUG-85941
Binding {
target: control
property: "checkable"
value: (control.action?.checkable ?? false) || (control.menuActions.length > 0)
restoreMode: Binding.RestoreBinding
}
// Important: This cannot be a direct onVisibleChanged handler in the button
// because it breaks action assignment if we do that. However, this slightly
// more indirect Connections object does not have that effect.
Connections {
target: control
function onVisibleChanged() {
if (!control.visible && control.menu && control.menu.visible) {
control.menu.dismiss()
}
}
}
onToggled: {
if (menuActions.length > 0 && menu) {
if (checked) {
control.menuAboutToShow();
menu.popup(control, 0, control.height)
} else {
menu.dismiss()
}
}
}
QQC2.ToolTip {
visible: control.hovered && text.length > 0 && !(control.menu && control.menu.visible) && !control.pressed
text: {
const a = control.action;
if (a) {
if (a.tooltip) {
return a.tooltip;
} else if (control.display === QQC2.Button.IconOnly) {
return a.text;
}
}
return "";
}
}
// This will set showMenuArrow when using qqc2-desktop-style.
Accessible.role: (control.showMenuArrow && control.menuActions.length > 0) ? Accessible.ButtonMenu : Accessible.Button
Accessible.ignored: !visible
Accessible.onPressAction: {
if (control.checkable) {
control.toggle();
} else {
control.action.trigger();
}
}
}
@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2023 Connor Carney <hello@connorcarney.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import QtQuick.Shapes as QQShapes
/**
* @brief A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
*/
Item {
id: root
//BEGIN properties
/**
* @brief The flickable that this indicator is attached to.
*
* If this is not set, the indicator will search for a Flickable in its parent chain.
*/
property Flickable flickable: {
let candidate = parent
while (candidate) {
if (candidate instanceof Flickable) {
return candidate
} else if (candidate instanceof Kirigami.ScrollablePage) {
return candidate.flickable
}
candidate = candidate.parent
}
return null;
}
/**
* @brief Whether to show the busy indicator at the top of the flickable
*
* This should be set to true whenever a refresh is in progress. It should typically
* be set to true whe triggered() is emitted, and set to false when the refresh is
* complete. This is not done automatically because the refresh may be triggered
* from outside the indicator.
*/
property bool active: false
/**
* @brief How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggered).
*/
readonly property real progress: !refreshing ? Math.min(-Math.min(flickable?.verticalOvershoot ?? 0, 0) / indicatorContainer.height, 1) : 0
/**
* @brief Time to wait after the flickable has been pulled down before triggering a refresh
*
* This gives the user a chance to back out of the refresh if they release the flickable
* before the refreshDelay has elapsed.
*/
property int refreshDelay: 500
/**
* @brief emitted when the flickable is pulled down far enough to trigger a refresh
*/
signal triggered()
//END properties
Item {
id: indicatorContainer
parent: root.flickable
anchors {
bottom: parent?.contentItem?.top
bottomMargin: root.flickable.topMargin
}
width: flickable?.width
height: Kirigami.Units.gridUnit * 4
QQC2.BusyIndicator {
id: busyIndicator
z: 1
anchors.centerIn: parent
running: root.active
visible: root.active
// Android busywidget QQC seems to be broken at custom sizes
}
QQShapes.Shape {
id: spinnerProgress
anchors {
fill: busyIndicator
margins: Kirigami.Units.smallSpacing
}
visible: !root.active && root.progress > 0
QQShapes.ShapePath {
strokeWidth: Kirigami.Units.smallSpacing
strokeColor: Kirigami.Theme.highlightColor
fillColor: "transparent"
PathAngleArc {
centerX: spinnerProgress.width / 2
centerY: spinnerProgress.height / 2
radiusX: spinnerProgress.width / 2 - Kirigami.Units.smallSpacing / 2
radiusY: spinnerProgress.height / 2 - Kirigami.Units.smallSpacing / 2
startAngle: 0
sweepAngle: 360 * root.progress
}
}
}
}
onProgressChanged: {
if (!root.active && root.progress >= 1) {
refreshTriggerTimer.running = true;
} else {
refreshTriggerTimer.running = false;
}
}
states: [
State {
name: "active"
when: root.active
PropertyChanges {
target: indicatorContainer
anchors.bottomMargin: root.flickable.topMargin - indicatorContainer.height
}
PropertyChanges {
target: root.flickable
explicit: true
// this is not a loop because of explicit:true above
// It adds the height of the indicator to the topMargin of the flickable
// when we enter the active state; the change is automatically reversed
// when returning to the base state.
topMargin: indicatorContainer.height + root.flickable.topMargin
}
}
]
transitions: [
Transition {
from: ""
to: "active"
enabled: root.flickable.verticalOvershoot >= 0
reversible: true
NumberAnimation {
target: root.flickable
properties: "topMargin"
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
]
Timer {
id: refreshTriggerTimer
interval: root.refreshDelay
onTriggered: {
if (!root.active && root.progress >= 1) {
root.triggered()
}
}
}
}
@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
MouseArea {
id: swipeFilter
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
z: 99999
property Item currentItem
property real peek
preventStealing: true
width: Kirigami.Units.gridUnit
onPressed: mouse => {
const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
}
onPositionChanged: mouse => {
const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
peek = 1 - mapped.x / parent.flickableItem.contentItem.width;
}
}
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Kirigami.AbstractApplicationHeader {
id: root
// anchors.fill: parent
property Item container
property bool current
minimumHeight: pageRow ? pageRow.globalToolBar.minimumHeight : Kirigami.Units.iconSizes.medium + Kirigami.Units.smallSpacing * 2
maximumHeight: pageRow ? pageRow.globalToolBar.maximumHeight : minimumHeight
preferredHeight: pageRow ? pageRow.globalToolBar.preferredHeight : minimumHeight
separatorVisible: pageRow ? pageRow.globalToolBar.separatorVisible : true
Kirigami.Theme.colorSet: pageRow ? pageRow.globalToolBar.colorSet : Kirigami.Theme.Header
leftPadding: pageRow
? Math.min(
width / 2,
Math.max(
(page.title.length > 0 ? pageRow.globalToolBar.titleLeftPadding : 0),
Qt.application.layoutDirection === Qt.LeftToRight
? Math.min(pageRow.globalToolBar.leftReservedSpace,
pageRow.Kirigami.ScenePosition.x
- page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.leftReservedSpace)
+ Kirigami.Units.smallSpacing
: Math.min(pageRow.globalToolBar.leftReservedSpace,
-pageRow.width
+ pageRow.Kirigami.ScenePosition.x
+ page.Kirigami.ScenePosition.x
+ page.width
+ pageRow.globalToolBar.leftReservedSpace)
+ Kirigami.Units.smallSpacing))
: Kirigami.Units.smallSpacing
rightPadding: pageRow
? Math.max(0,
Qt.application.layoutDirection === Qt.LeftToRight
? (-pageRow.width
- pageRow.Kirigami.ScenePosition.x
+ page.width
+ page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.rightReservedSpace)
: (pageRow.Kirigami.ScenePosition.x
- page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.rightReservedSpace))
: 0
}
@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
ListView {
id: root
readonly property Kirigami.PageRow pageRow: {
// This is fetched from breadcrumbLoader in PageRowGlobalToolBarUI.qml
const pr = parent?.pageRow ?? null;
return pr as Kirigami.PageRow;
}
currentIndex: {
if (!pageRow) {
return -1;
}
// This ListView is eventually consistent with PageRow, so it has to
// force-refresh currentIndex when its count finally catches up,
// otherwise currentIndex might get reset and stuck at -1.
void count;
// TODO: This "eventual consistency" causes Behavior on contentX to
// scroll from the start each time a page is added. Besides, simple
// number is not the most efficient model, because ListView
// recreates all delegates when number changes.
if (pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, depth has to be adjusted by 1.
// In case of layers, current index is always the last one,
// which is one less than their count, thus minus another 1.
return pageRow.layers.depth - 2;
} else {
return pageRow.currentIndex;
}
}
// This function exists outside of delegate, so that when popping layers
// the JavaScript execution context won't be destroyed along with delegate.
function selectIndex(index: int) {
if (!pageRow) {
return;
}
if (pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, index has to be adjusted by 1.
// We want to pop anything after selected index,
// turning selected layer into current one, thus plus another 1.
while (pageRow.layers.depth > index + 2) {
pageRow.layers.pop();
}
} else {
pageRow.currentIndex = index;
}
}
contentHeight: height
clip: true
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
interactive: Kirigami.Settings.hasTransientTouchInput
contentX: {
if (!currentItem) {
return 0;
}
// preferred position: current item is centered within viewport
const preferredPosition = currentItem.x + (currentItem.width - width) / 2;
// Note: Order of min/max is important. Make sure to test on all sorts
// and sizes before committing changes to this formula.
if (LayoutMirroring.enabled) {
// In a mirrored ListView contentX starts from left edge and increases to the left.
const maxLeftPosition = -contentWidth;
const minRightPosition = -width;
return Math.round(Math.min(minRightPosition, Math.max(preferredPosition, maxLeftPosition)));
} else {
const minLeftPosition = 0;
const maxRightPosition = contentWidth - width;
return Math.round(Math.max(minLeftPosition, Math.min(preferredPosition, maxRightPosition)));
}
}
Behavior on contentX {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
model: {
if (!root.pageRow) {
return null;
}
if (root.pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView; ignore it.
return root.pageRow.layers.depth - 1;
} else {
return root.pageRow.depth;
}
}
delegate: MouseArea {
id: delegate
required property int index
// We can't use Kirigami.Page here instead of Item since we now accept
// pushing PageRow to a new layer.
readonly property Item page: {
if (!root.pageRow) {
return null;
}
if (root.pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, index has to be adjusted by 1.
return pageRow.layers.get(index + 1);
} else {
return pageRow.get(index);
}
}
width: Math.ceil(layout.implicitWidth)
height: ListView.view?.height ?? 0
hoverEnabled: !Kirigami.Settings.tabletMode
onClicked: mouse => {
root.selectIndex(index);
}
// background
Rectangle {
color: Kirigami.Theme.highlightColor
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
opacity: root.count > 1 && parent.containsMouse ? 0.1 : 0
}
// content
RowLayout {
id: layout
anchors.fill: parent
spacing: 0
Kirigami.Icon {
visible: delegate.index > 0
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.preferredWidth: Kirigami.Units.iconSizes.small
isMask: true
color: Kirigami.Theme.textColor
source: LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic"
}
Kirigami.Heading {
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
color: Kirigami.Theme.textColor
verticalAlignment: Text.AlignVCenter
wrapMode: Text.NoWrap
text: delegate.page?.title ?? ""
opacity: delegate.ListView.isCurrentItem ? 1 : 0.4
}
}
}
}
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
QtObject {
id: globalToolBar
property int style: Kirigami.ApplicationHeaderStyle.None
readonly property int actualStyle: {
if (style === Kirigami.ApplicationHeaderStyle.Auto) {
if (!Kirigami.Settings.isMobile) {
return Kirigami.ApplicationHeaderStyle.ToolBar
} else if (root.wideMode) {
return Kirigami.ApplicationHeaderStyle.Titles
} else {
return Kirigami.ApplicationHeaderStyle.Breadcrumb
}
}
return style;
}
/** @property kirigami::ApplicationHeaderStyle::NavigationButtons */
property int showNavigationButtons: (!Kirigami.Settings.isMobile || Qt.platform.os === "ios")
? (Kirigami.ApplicationHeaderStyle.ShowBackButton | Kirigami.ApplicationHeaderStyle.ShowForwardButton)
: Kirigami.ApplicationHeaderStyle.NoNavigationButtons
property bool separatorVisible: true
//Unfortunately we can't access pageRow.globalToolbar.Kirigami.Theme directly in a declarative way
property int colorSet: Kirigami.Theme.Header
// whether or not the header should be
// "pushed" back when scrolling using the
// touch screen
property bool hideWhenTouchScrolling: false
/**
* If true, when any kind of toolbar is shown, the drawer handles will be shown inside the toolbar, if they're present
*/
property bool canContainHandles: true
property int toolbarActionAlignment: Qt.AlignRight
property int toolbarActionHeightMode: Kirigami.ToolBarLayout.ConstrainIfLarger
property int minimumHeight: 0
// FIXME: Figure out the exact standard size of a Toolbar
property int preferredHeight: (actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar
? Kirigami.Units.iconSizes.medium
: Kirigami.Units.gridUnit * 1.8) + Kirigami.Units.smallSpacing * 2
property int maximumHeight: preferredHeight
// Sets the minimum leading padding for the title in a page header
property int titleLeftPadding: Kirigami.Units.gridUnit
}
@@ -0,0 +1,157 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import "../../templates" as KT
import "../../templates/private" as TP
import "../" as P
Kirigami.AbstractApplicationHeader {
id: header
readonly property int leftReservedSpace: (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0 ? buttonsLayout.width + Kirigami.Units.smallSpacing : 0) // Take into account the layout margins the nav buttons have
+ (leftHandleAnchor.visible ? leftHandleAnchor.width : 0)
+ (menuButton.visible ? menuButton.width : 0)
readonly property int rightReservedSpace: rightHandleAnchor.visible ? rightHandleAnchor.width + Kirigami.Units.smallSpacing : 0
readonly property alias leftHandleAnchor: leftHandleAnchor
readonly property alias rightHandleAnchor: rightHandleAnchor
readonly property bool breadcrumbVisible: layerIsMainRow && breadcrumbLoader.active
readonly property bool layerIsMainRow: (root.layers.currentItem.hasOwnProperty("columnView")) ? root.layers.currentItem.columnView === root.columnView : false
readonly property Item currentItem: layerIsMainRow ? root.columnView : root.layers.currentItem
function __shouldHandleAnchorBeVisible(handleAnchor: Item, drawerProperty: string, itemProperty: string): bool {
if (typeof applicationWindow === "undefined") {
return false;
}
const w = applicationWindow();
if (!w) {
return false;
}
const drawer = w[drawerProperty] as KT.OverlayDrawer;
if (!drawer || !drawer.enabled || !drawer.handleVisible || drawer.handle.handleAnchor !== handleAnchor) {
return false;
}
const item = breadcrumbLoader.pageRow?.[itemProperty] as Item;
const style = item?.globalToolBarStyle ?? Kirigami.ApplicationHeaderStyle.None;
return globalToolBar.canContainHandles || style === Kirigami.ApplicationHeaderStyle.ToolBar;
}
height: visible ? implicitHeight : 0
minimumHeight: globalToolBar.minimumHeight
preferredHeight: globalToolBar.preferredHeight
maximumHeight: globalToolBar.maximumHeight
separatorVisible: globalToolBar.separatorVisible
Kirigami.Theme.colorSet: globalToolBar.colorSet
RowLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.preferredWidth: applicationWindow().pageStack.globalToolBar.leftReservedSpace
visible: applicationWindow().pageStack !== root
}
Item {
id: leftHandleAnchor
visible: header.__shouldHandleAnchorBeVisible(leftHandleAnchor, "globalDrawer", "leadingVisibleItem")
Layout.preferredHeight: Math.max(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
}
P.PrivateActionToolButton {
id: menuButton
visible: !Kirigami.Settings.isMobile && applicationWindow().globalDrawer && "isMenu" in applicationWindow().globalDrawer && applicationWindow().globalDrawer.isMenu
icon.name: "open-menu-symbolic"
showMenuArrow: false
Layout.preferredHeight: Math.min(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
Layout.leftMargin: Kirigami.Units.smallSpacing
action: Kirigami.Action {
children: applicationWindow().globalDrawer && applicationWindow().globalDrawer.actions ? applicationWindow().globalDrawer.actions : []
tooltip: checked ? qsTr("Close menu") : qsTr("Open menu")
}
Accessible.name: action.tooltip
Connections {
// Only target the GlobalDrawer when it *is* a GlobalDrawer, since
// it can be something else, and that something else probably
// doesn't have an isMenuChanged() signal.
target: applicationWindow().globalDrawer as Kirigami.GlobalDrawer
function onIsMenuChanged() {
if (!applicationWindow().globalDrawer.isMenu && menuButton.menu) {
menuButton.menu.dismiss()
}
}
}
}
RowLayout {
id: buttonsLayout
Layout.fillHeight: true
Layout.preferredHeight: Math.max(backButton.visible ? backButton.implicitHeight : 0, forwardButton.visible ? forwardButton.implicitHeight : 0)
Layout.leftMargin: leftHandleAnchor.visible ? Kirigami.Units.smallSpacing : 0
visible: (globalToolBar.showNavigationButtons !== Kirigami.ApplicationHeaderStyle.NoNavigationButtons || applicationWindow().pageStack.layers.depth > 1 && !(applicationWindow().pageStack.layers.currentItem instanceof Kirigami.PageRow || header.layerIsMainRow))
&& globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None
Layout.maximumWidth: visibleChildren.length > 0 ? Layout.preferredWidth : 0
TP.BackButton {
id: backButton
Layout.leftMargin: leftHandleAnchor.visible ? 0 : Kirigami.Units.smallSpacing
Layout.minimumWidth: implicitHeight
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: buttonsLayout.height
}
TP.ForwardButton {
id: forwardButton
Layout.minimumWidth: implicitHeight
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: buttonsLayout.height
}
}
QQC2.ToolSeparator {
visible: (menuButton.visible || (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0)) && breadcrumbVisible && pageRow.depth > 1
}
Loader {
id: breadcrumbLoader
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: -1
Layout.preferredHeight: -1
property Kirigami.PageRow pageRow: root
asynchronous: true
active: globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.Breadcrumb
&& header.currentItem
&& header.currentItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.None
source: Qt.resolvedUrl("BreadcrumbControl.qml")
}
Item {
id: rightHandleAnchor
visible: header.__shouldHandleAnchorBeVisible(rightHandleAnchor, "contextDrawer", "trailingVisibleItem")
Layout.preferredHeight: Math.max(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
}
}
background.opacity: breadcrumbLoader.active ? 1 : 0
}
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
AbstractPageHeader {
id: root
Loader {
id: titleLoader
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
}
height: Math.min(root.height, item
? (item.Layout.preferredHeight > 0 ? item.Layout.preferredHeight : item.implicitHeight)
: 0)
// Don't load async to prevent jumpy behaviour on slower devices as it loads in.
// If the title delegate really needs to load async, it should be its responsibility to do it itself.
asynchronous: false
sourceComponent: page ? page.titleDelegate : null
}
}
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolBar {
id: root
position: QQC2.ToolBar.Footer
NumberAnimation {
id: appearAnim
target: root
property: "height"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
Connections {
target: applicationWindow()
function onControlsVisibleChanged() {
if (applicationWindow().controlsVisible) {
appearAnim.from = 0;
appearAnim.to = root.implicitHeight;
} else {
appearAnim.from = root.implicitHeight;
appearAnim.to = 0;
}
appearAnim.restart();
}
}
contentItem: Kirigami.ActionToolBar {
display: QQC2.Button.TextUnderIcon
alignment: Qt.AlignCenter
actions: root.parent.page.actions
}
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
AbstractPageHeader {
id: root
implicitWidth: layout.implicitWidth + Kirigami.Units.smallSpacing * 2
implicitHeight: Math.max(titleLoader.implicitHeight, toolBar.implicitHeight) + Kirigami.Units.smallSpacing * 2
MouseArea {
anchors.fill: parent
onPressed: mouse => {
page.forceActiveFocus()
mouse.accepted = false
}
}
RowLayout {
id: layout
anchors.fill: parent
anchors.rightMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Loader {
id: titleLoader
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: item?.Layout.fillWidth ?? false
Layout.minimumWidth: item?.Layout.minimumWidth ?? -1
Layout.preferredWidth: item?.Layout.preferredWidth ?? -1
Layout.maximumWidth: item?.Layout.maximumWidth ?? -1
// Don't load async to prevent jumpy behaviour on slower devices as it loads in.
// If the title delegate really needs to load async, it should be its responsibility to do it itself.
asynchronous: false
sourceComponent: page?.titleDelegate ?? null
}
Kirigami.ActionToolBar {
id: toolBar
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
visible: actions.length > 0
alignment: pageRow?.globalToolBar.toolbarActionAlignment ?? Qt.AlignRight
heightMode: pageRow?.globalToolBar.toolbarActionHeightMode ?? Kirigami.ToolBarLayout.ConstrainIfLarger
actions: page?.actions ?? []
}
}
}
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* @brief An item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away)
* It's a behavior similar to the typical mobile web browser addressbar
* 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
*
* @inherit QtQuick.Item
*/
Item {
id: root
z: 90
property int minimumHeight: 0
// Use an inline arrow function, referring to an external normal function makes QV4 crash, see https://bugreports.qt.io/browse/QTBUG-119395
property int preferredHeight: mainItem.children.reduce((accumulator, item) => {
return Math.max(accumulator, item.implicitHeight);
}, 0) + topPadding + bottomPadding
property int maximumHeight: Kirigami.Units.gridUnit * 3
property int position: QQC2.ToolBar.Header
property Kirigami.PageRow pageRow: __appWindow?.pageStack ?? null
property Kirigami.Page page: pageRow?.currentItem as Kirigami.Page ?? null
default property alias contentItem: mainItem.data
readonly property int paintedHeight: headerItem.y + headerItem.height - 1
property int leftPadding: 0
property int topPadding: 0
property int rightPadding: 0
property int bottomPadding: 0
property bool separatorVisible: true
/**
* This property specifies whether the header should be pushed back when
* scrolling using the touch screen.
*/
property bool hideWhenTouchScrolling: root.pageRow?.globalToolBar.hideWhenTouchScrolling ?? false
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
Kirigami.Theme.inherit: true
// FIXME: remove
property QtObject __appWindow: typeof applicationWindow !== "undefined" ? applicationWindow() : null
implicitHeight: preferredHeight
height: Layout.preferredHeight
/**
* @brief This property holds the background item.
* @note the background will be automatically sized to fill the whole control
*/
property Item background
onBackgroundChanged: {
background.z = -1;
background.parent = headerItem;
background.anchors.fill = headerItem;
}
Component.onCompleted: AppHeaderSizeGroup.items.push(this)
onMinimumHeightChanged: implicitHeight = preferredHeight;
onPreferredHeightChanged: implicitHeight = preferredHeight;
opacity: height > 0 ? 1 : 0
NumberAnimation {
id: heightAnim
target: root
property: "implicitHeight"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
Connections {
target: root.__appWindow
function onControlsVisibleChanged() {
heightAnim.from = root.implicitHeight;
heightAnim.to = root.__appWindow.controlsVisible ? root.preferredHeight : 0;
heightAnim.restart();
}
}
Connections {
target: root.page?.Kirigami.ColumnView ?? null
function onScrollIntention(event) {
headerItem.scrollIntentHandler(event);
}
}
Item {
id: headerItem
anchors {
left: parent.left
right: parent.right
bottom: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Header ? parent.bottom : undefined
top: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Footer ? parent.top : undefined
}
height: Math.max(root.height, root.minimumHeight > 0 ? root.minimumHeight : root.preferredHeight)
function scrollIntentHandler(event) {
if (!root.hideWhenTouchScrolling) {
return
}
if (root.pageRow
&& root.pageRow.globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.Breadcrumb) {
return;
}
if (!root.page.flickable || (root.page.flickable.atYBeginning && root.page.flickable.atYEnd)) {
return;
}
root.implicitHeight = Math.max(0, Math.min(root.preferredHeight, root.implicitHeight + event.delta.y))
event.accepted = root.implicitHeight > 0 && root.implicitHeight < root.preferredHeight;
slideResetTimer.restart();
if ((root.page.flickable instanceof ListView) && root.page.flickable.verticalLayoutDirection === ListView.BottomToTop) {
root.page.flickable.contentY -= event.delta.y;
}
}
Connections {
target: root.page?.globalToolBarItem ?? null
enabled: target
function onImplicitHeightChanged() {
root.implicitHeight = root.page.globalToolBarItem.implicitHeight;
}
}
Timer {
id: slideResetTimer
interval: 500
onTriggered: {
if ((root.pageRow?.wideMode ?? (root.__appWindow?.wideScreen ?? false)) || !Kirigami.Settings.isMobile) {
return;
}
if (root.height > root.minimumHeight + (root.preferredHeight - root.minimumHeight) / 2) {
heightAnim.to = root.preferredHeight;
} else {
heightAnim.to = root.minimumHeight;
}
heightAnim.from = root.implicitHeight
heightAnim.restart();
}
}
Connections {
target: pageRow
function onCurrentItemChanged() {
if (!root.page) {
return;
}
heightAnim.from = root.implicitHeight;
heightAnim.to = root.preferredHeight;
heightAnim.restart();
}
}
Item {
id: mainItem
clip: childrenRect.width > width
onChildrenChanged: {
for (const child of children) {
child.anchors.verticalCenter = verticalCenter;
}
}
anchors {
fill: parent
topMargin: root.topPadding
leftMargin: root.leftPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
}
}
}
@@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* A AbstractCard is the base for cards. A Card is a visual object that serves
* as an entry point for more detailed information. An abstractCard is empty,
* providing just the look and the base properties and signals for an ItemDelegate.
* It can be filled with any custom layout of items, its content is organized
* in 3 properties: header, contentItem and footer.
* Use this only when you need particular custom contents, for a standard layout
* for cards, use the Card component.
*
* @see Card
* @inherit QtQuick.Controls.ItemDelegate
* @since 2.4
*/
T.ItemDelegate {
id: root
//BEGIN properties
/**
* @brief This property holds an item that serves as a header.
*
* This item will be positioned on top if headerOrientation is ``Qt.Vertical``
* or on the left if it is ``Qt.Horizontal``.
*/
property alias header: headerFooterLayout.header
/**
* @brief This property sets the card's orientation.
*
* * ``Qt.Vertical``: the header will be positioned on top
* * ``Qt.Horizontal``: the header will be positioned on the left (or right if an RTL layout is used)
*
* default: ``Qt.Vertical``
*
* @property Qt::Orientation headerOrientation
*/
property int headerOrientation: Qt.Vertical
/**
* @brief This property holds an item that serves as a footer.
*
* This item will be positioned at the bottom if headerOrientation is ``Qt.Vertical``
* or on the right if it is ``Qt.Horizontal``.
*/
property alias footer: headerFooterLayout.footer
/**
* @brief This property sets whether clicking or tapping on the card area shows a visual click feedback.
*
* Use this if you want to do an action in the onClicked signal handler of the card.
*
* default: ``false``
*/
property bool showClickFeedback: false
//END properties
Layout.fillWidth: true
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
outerPaddingLayout.implicitWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
outerPaddingLayout.implicitHeight)
hoverEnabled: !Kirigami.Settings.tabletMode && showClickFeedback
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
width: ListView.view ? ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin : undefined
padding: Kirigami.Units.largeSpacing
// Card component repurposes control's contentItem property, so it has to
// reimplement content layout and its padding manually.
Kirigami.Padding {
id: outerPaddingLayout
anchors.fill: parent
topPadding: root.topPadding
leftPadding: root.leftPadding
rightPadding: root.rightPadding
bottomPadding: root.bottomPadding
contentItem: Kirigami.HeaderFooterLayout {
id: headerFooterLayout
contentItem: Kirigami.Padding {
id: innerPaddingLayout
contentItem: root.contentItem
// Hide it altogether, so that vertical padding won't be
// included in control's total implicit height.
visible: contentItem !== null
topPadding: headerFooterLayout.header ? Kirigami.Units.largeSpacing : 0
bottomPadding: headerFooterLayout.footer ? Kirigami.Units.largeSpacing : 0
}
}
}
// HACK: A Control like this ItemDelegate tries to manage its
// contentItem's positioning, so we need to override that. This is
// equivalent to declaring x/y/width/height bindings in QQC2 style
// implementations.
Connections {
target: root.contentItem
function onXChanged() {
root.contentItem.x = 0;
}
function onYChanged() {
root.contentItem.y = Qt.binding(() => innerPaddingLayout.topPadding);
}
}
}
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Templates as T
/**
* @brief Chip is a visual object based on AbstractButton
* that provides a friendly way to display predetermined elements
* with the visual styling of "tags" or "tokens."
*
* @see Chip
* @since 2.19
* @inherit QtQuick.Controls.AbstractButton
*/
T.AbstractButton {
id: chip
/**
* @brief This property holds whether or not to display a close button.
*
* default: ``true``
*/
property bool closable: true
/**
* @brief This property holds whether the icon should be masked or not. This controls the Kirigami.Icon.isMask property.
*
* default: ``false``
*/
property bool iconMask: false
/**
* @brief This signal is emitted when the close button has been clicked.
*/
signal removed()
}
@@ -0,0 +1,411 @@
/*
* SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates.private as TP
/**
* An inline message item with support for informational, positive,
* warning and error types, and with support for associated actions.
*
* InlineMessage can be used to give information to the user or
* interact with the user, without requiring the use of a dialog.
*
* The InlineMessage item is hidden by default. It also manages its
* height (and implicitHeight) during an animated reveal when shown.
* You should avoid setting height on an InlineMessage unless it is
* already visible.
*
* Optionally an icon can be set, defaulting to an icon appropriate
* to the message type otherwise.
*
* Optionally a close button can be shown.
*
* Actions are added from left to right. If more actions are set than
* can fit, an overflow menu is provided.
*
* Example:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.InlineMessage {
* type: Kirigami.MessageType.Error
*
* text: i18n("My error message")
*
* actions: [
* Kirigami.Action {
* icon.name: "list-add"
* text: i18n("Add")
* onTriggered: source => {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: i18n("Edit")
* onTriggered: source => {
* // do stuff
* }
* }
* ]
* }
* @endcode
*
* @since 5.45
* @inherit QtQuick.Templates.Control
*/
T.Control {
id: root
visible: false
/**
* Defines a position for the message: whether it's to be used as an inline component inside the page,
* a page header, or a page footer.
*/
enum Position {
Inline,
Header,
Footer
}
/**
* Adjust the look of the message based upon the position.
* If a message is positioned in the header area or in the footer area
* of a page, it might be desirable to not have borders but just a line
* separating it from the content area. In this case, use the Header or
* Footer position.
* Default is InlineMessage.Position.Inline
*/
property int position: InlineMessage.Position.Inline
/**
* This signal is emitted when a link is hovered in the message text.
* @param The hovered link.
*/
signal linkHovered(string link)
/**
* This signal is emitted when a link is clicked or tapped in the message text.
* @param The clicked or tapped link.
*/
signal linkActivated(string link)
/**
* This property holds the link embedded in the message text that the user is hovering over.
*/
readonly property alias hoveredLink: label.hoveredLink
/**
* This property holds the message type. One of Information, Positive, Warning or Error.
*
* The default is Kirigami.MessageType.Information.
*/
property int type: Kirigami.MessageType.Information
/**
* This grouped property holds the description of an optional icon.
*
* * source: The source of the icon, a freedesktop-compatible icon name is recommended.
* * color: An optional tint color for the icon.
*
* If no custom icon is set, an icon appropriate to the message type
* is shown.
*/
property TP.IconPropertiesGroup icon: TP.IconPropertiesGroup {}
/**
* This property holds the message text.
*/
property string text
/**
* This property holds whether the close button is displayed.
*
* The default is false.
*/
property bool showCloseButton: false
/**
* This property holds the list of actions to show. Actions are added from left to
* right. If more actions are set than can fit, an overflow menu is
* provided.
*/
property list<T.Action> actions
/**
* This property holds whether the current message item is animating.
*/
readonly property bool animating: _animating
property bool _animating: false
implicitHeight: visible ? (contentLayout.implicitHeight + topPadding + bottomPadding) : 0
padding: Kirigami.Units.smallSpacing
Accessible.role: Accessible.AlertMessage
Accessible.ignored: !visible
Behavior on implicitHeight {
enabled: !root.visible
SequentialAnimation {
PropertyAction { targets: root; property: "_animating"; value: true }
NumberAnimation { duration: Kirigami.Units.longDuration }
}
}
onVisibleChanged: {
if (!visible) {
contentLayout.opacity = 0;
}
}
opacity: visible ? 1 : 0
Behavior on opacity {
enabled: !root.visible
NumberAnimation { duration: Kirigami.Units.shortDuration }
}
onOpacityChanged: {
if (opacity === 0) {
contentLayout.opacity = 0;
} else if (opacity === 1) {
contentLayout.opacity = 1;
}
}
onImplicitHeightChanged: {
height = implicitHeight;
}
contentItem: Item {
id: contentLayout
// Used to defer opacity animation until we know if InlineMessage was
// initialized visible.
property bool complete: false
Behavior on opacity {
enabled: root.visible && contentLayout.complete
SequentialAnimation {
NumberAnimation { duration: Kirigami.Units.shortDuration * 2 }
PropertyAction { targets: root; property: "_animating"; value: false }
}
}
implicitHeight: {
if (atBottom) {
return label.implicitHeight + actionsLayout.implicitHeight + actionsLayout.anchors.topMargin
} else {
return Math.max(icon.implicitHeight, label.implicitHeight, closeButton.implicitHeight, actionsLayout.implicitHeight)
}
}
Accessible.ignored: true
readonly property real remainingWidth: width - (
icon.width
+ label.anchors.leftMargin + label.implicitWidth + label.anchors.rightMargin
+ (root.showCloseButton ? closeButton.width : 0)
)
readonly property bool multiline: remainingWidth <= 0 || atBottom
readonly property bool atBottom: (root.actions.length > 0) && (label.lineCount > 1 || actionsLayout.implicitWidth > remainingWidth)
Kirigami.Icon {
id: icon
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
anchors {
left: parent.left
leftMargin: Kirigami.Units.smallSpacing
topMargin: Kirigami.Units.smallSpacing
}
states: [
State {
name: "multi-line"
when: contentLayout.atBottom || label.height > icon.height * 1.7
AnchorChanges {
target: icon
anchors.top: icon.parent.top
anchors.verticalCenter: undefined
}
},
// States are evaluated in the order they are declared.
// This is a fallback state.
State {
name: "single-line"
when: true
AnchorChanges {
target: icon
anchors.top: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
]
source: {
if (root.icon.name) {
return root.icon.name;
} else if (root.icon.source) {
return root.icon.source;
}
switch (root.type) {
case Kirigami.MessageType.Positive:
return "emblem-success";
case Kirigami.MessageType.Warning:
return "emblem-warning";
case Kirigami.MessageType.Error:
return "emblem-error";
default:
return "emblem-information";
}
}
color: root.icon.color
Accessible.ignored: !root.visible
Accessible.name: {
switch (root.type) {
case Kirigami.MessageType.Positive:
return qsTr("Success");
case Kirigami.MessageType.Warning:
return qsTr("Warning");
case Kirigami.MessageType.Error:
return qsTr("Error");
default:
return qsTr("Note");
}
}
}
Kirigami.SelectableLabel {
id: label
anchors {
left: icon.right
leftMargin: Kirigami.Units.largeSpacing
right: root.showCloseButton ? closeButton.left : parent.right
rightMargin: root.showCloseButton ? Kirigami.Units.smallSpacing : 0
top: parent.top
}
color: Kirigami.Theme.textColor
wrapMode: Text.WordWrap
text: root.text
verticalAlignment: Text.AlignVCenter
// QTBUG-117667 TextEdit (super-type of SelectableLabel) needs
// very specific state-management trick so it doesn't get stuck.
// State names serve purely as a description.
states: [
State {
name: "multi-line"
when: contentLayout.multiline
AnchorChanges {
target: label
anchors.bottom: undefined
}
PropertyChanges {
target: label
height: label.implicitHeight
}
},
// States are evaluated in the order they are declared.
// This is a fallback state.
State {
name: "single-line"
when: true
AnchorChanges {
target: label
anchors.bottom: label.parent.bottom
}
}
]
onLinkHovered: link => root.linkHovered(link)
onLinkActivated: link => root.linkActivated(link)
Accessible.ignored: !root.visible
}
Kirigami.ActionToolBar {
id: actionsLayout
flat: false
actions: root.actions
visible: root.actions.length > 0
Accessible.ignored: !visible || !root.visible
alignment: Qt.AlignRight
anchors {
left: parent.left
top: contentLayout.atBottom ? label.bottom : parent.top
topMargin: contentLayout.atBottom ? Kirigami.Units.largeSpacing : 0
right: (!contentLayout.atBottom && root.showCloseButton) ? closeButton.left : parent.right
rightMargin: !contentLayout.atBottom && root.showCloseButton ? Kirigami.Units.smallSpacing : 0
}
}
QQC2.ToolButton {
id: closeButton
visible: root.showCloseButton
anchors.right: parent.right
// Incompatible anchors need to be evaluated in a given order,
// which simple declarative bindings cannot assure
states: [
State {
name: "onTop"
when: contentLayout.atBottom
AnchorChanges {
target: closeButton
anchors.top: parent.top
anchors.verticalCenter: undefined
}
} ,
State {
name: "centered"
AnchorChanges {
target: closeButton
anchors.top: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
]
height: contentLayout.atBottom ? implicitHeight : implicitHeight
text: qsTr("Close")
display: QQC2.ToolButton.IconOnly
icon.name: "dialog-close"
onClicked: root.visible = false
Accessible.ignored: !root.visible
}
Component.onCompleted: complete = true
}
}
@@ -0,0 +1,385 @@
/*
* SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Templates as T
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as KTP
/**
* Overlay Drawers are used to expose additional UI elements needed for
* small secondary tasks for which the main UI elements are not needed.
* For example in Okular Mobile, an Overlay Drawer is used to display
* thumbnails of all pages within a document along with a search field.
* This is used for the distinct task of navigating to another page.
*
* @inherit QtQuick.Controls.Drawer
*/
T.Drawer {
id: root
//BEGIN properties
/**
* @brief This property tells whether the drawer is open and visible.
*
* default: ``false``
*/
property bool drawerOpen: false
/**
* @brief This property tells whether the drawer is in a state between open
* and closed.
*
* The drawer is visible but not completely open. This is usually the case when
* the user is dragging the drawer from a screen edge, so the user is "peeking"
* at what's in the drawer.
*
* default: ``false``
*/
property bool peeking: false
/**
* @brief This property tells whether the drawer is currently opening or closing itself.
*/
readonly property bool animating : enterAnimation.animating || exitAnimation.animating || positionResetAnim.running
/**
* @brief This property holds whether the drawer can be collapsed to a
* very thin, usually icon only sidebar.
*
* Only modal drawers are collapsible. Collapsible is not supported in
* the mobile mode.
*
* @since 2.5
*/
property bool collapsible: false
/**
* @brief This property tells whether the drawer is collapsed to a
* very thin sidebar, usually icon only.
*
* When true, the drawer will be collapsed to a very thin sidebar,
* usually icon only.
*
* default: ``false``
*
* @see collapsible Only collapsible drawers can be collapsed.
*/
property bool collapsed: false
/**
* @brief This property holds the size of the collapsed drawer.
*
* For vertical drawers this will be the width of the drawer and for horizontal
* drawers this will be the height of the drawer.
*
* default: ``Units.iconSizes.medium``, just enough to accommodate medium sized icons
*/
property int collapsedSize: Kirigami.Units.iconSizes.medium
/**
* @brief This property holds the options for handle's open icon.
*
* This is a grouped property with following components:
*
* * ``source: var``: The name of a freedesktop-compatible icon.
* * ``color: color``: An optional tint color for the icon.
*
* If no custom icon is set, a menu icon is shown for the application globalDrawer
* and an overflow menu icon is shown for the contextDrawer.
* That's the default for the GlobalDrawer and ContextDrawer components respectively.
*
* For OverlayDrawer the default is view-right-close or view-left-close depending on
* the drawer location
*
* @since 2.5
*/
readonly property KTP.IconPropertiesGroup handleOpenIcon: KTP.IconPropertiesGroup {
source: root.edge === Qt.RightEdge ? "view-right-close" : "view-left-close"
}
/**
* @brief This property holds the options for the handle's close icon.
*
* This is a grouped property with the following components:
* * ``source: var``: The name of a freedesktop-compatible icon.
* * ``color: color``: An optional tint color for the icon.
*
* If no custom icon is set, an X icon is shown,
* which will morph into the Menu or overflow icons.
*
* For OverlayDrawer the default is view-right-new or view-left-new depending on
* the drawer location.
*
* @since 2.5
*/
property KTP.IconPropertiesGroup handleClosedIcon: KTP.IconPropertiesGroup {
source: root.edge === Qt.RightEdge ? "view-right-new" : "view-left-new"
}
/**
* @brief This property holds the tooltip displayed when the drawer is open.
* @since 2.15
*/
property string handleOpenToolTip: qsTr("Close drawer")
/**
* @brief This property holds the tooltip displayed when the drawer is closed.
* @since 2.15
*/
property string handleClosedToolTip: qsTr("Open drawer")
/**
* @brief This property holds whether the handle is visible, to make opening the
* drawer easier.
*
* Currently supported only on left and right drawers.
*/
property bool handleVisible: {
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w) {
return w.controlsVisible;
}
}
// For a generic-purpose OverlayDrawer its handle is visible by default
return true;
}
/**
* @brief Readonly property that points to the item that will act as a physical
* handle for the Drawer.
* @property MouseArea handle
**/
readonly property Item handle: KTP.DrawerHandle {
drawer: root
}
//END properties
interactive: modal
z: Kirigami.OverlayZStacking.z
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: modal ? Kirigami.Theme.View : Kirigami.Theme.Window
Kirigami.Theme.onColorSetChanged: {
contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
}
//BEGIN reassign properties
//default paddings
leftPadding: Kirigami.Units.smallSpacing
topPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
y: modal ? 0 : ((T.ApplicationWindow.menuBar && T.ApplicationWindow.menuBar.visible ? T.ApplicationWindow.menuBar.height : 0) + (T.ApplicationWindow.header && T.ApplicationWindow.header.visible ? T.ApplicationWindow.header.height : 0))
height: parent && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) ? (modal ? parent.height : (parent.height - y - (T.ApplicationWindow.footer ? T.ApplicationWindow.footer.height : 0))) : implicitHeight
parent: modal || edge === Qt.LeftEdge || edge === Qt.RightEdge ? T.ApplicationWindow.overlay : T.ApplicationWindow.contentItem
edge: Qt.LeftEdge
modal: true
dim: modal
QQC2.Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.35)
}
dragMargin: enabled && (edge === Qt.LeftEdge || edge === Qt.RightEdge) ? Math.min(Kirigami.Units.gridUnit, Qt.styleHints.startDragDistance) : 0
contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
implicitWidth: contentWidth + leftPadding + rightPadding
implicitHeight: contentHeight + topPadding + bottomPadding
enter: Transition {
SequentialAnimation {
id: enterAnimation
/* NOTE: why this? the running status of the enter transition is not relaible and
* the SmoothedAnimation is always marked as non running,
* so the only way to get to a reliable animating status is with this
*/
property bool animating
ScriptAction {
script: {
enterAnimation.animating = true
// on non modal dialog we don't want drawers in the overlay
if (!root.modal) {
root.background.parent.parent = applicationWindow().overlay.parent
}
}
}
SmoothedAnimation {
velocity: 5
}
ScriptAction {
script: enterAnimation.animating = false
}
}
}
exit: Transition {
SequentialAnimation {
id: exitAnimation
property bool animating
ScriptAction {
script: exitAnimation.animating = true
}
SmoothedAnimation {
velocity: 5
}
ScriptAction {
script: exitAnimation.animating = false
}
}
}
//END reassign properties
//BEGIN signal handlers
onCollapsedChanged: {
if (Kirigami.Settings.isMobile) {
collapsed = false;
}
if (!__internal.completed) {
return;
}
if ((!collapsible || modal) && collapsed) {
collapsed = true;
}
}
onCollapsibleChanged: {
if (Kirigami.Settings.isMobile) {
collapsible = false;
}
if (!__internal.completed) {
return;
}
if (!collapsible) {
collapsed = false;
} else if (modal) {
collapsible = false;
}
}
onModalChanged: {
if (!__internal.completed) {
return;
}
if (modal) {
collapsible = false;
}
}
onPositionChanged: {
if (peeking) {
visible = true
}
}
onVisibleChanged: {
if (peeking) {
visible = true
} else {
drawerOpen = visible;
}
}
onPeekingChanged: {
if (peeking) {
root.enter.enabled = false;
root.exit.enabled = false;
} else {
drawerOpen = position > 0.5 ? 1 : 0;
positionResetAnim.running = true
root.enter.enabled = true;
root.exit.enabled = true;
}
}
onDrawerOpenChanged: {
// sync this property only when the component is properly loaded
if (!__internal.completed) {
return;
}
positionResetAnim.running = false;
if (drawerOpen) {
open();
} else {
close();
}
Qt.callLater(() => root.handle.displayToolTip = true)
}
Component.onCompleted: {
// if defined as drawerOpen by default in QML, don't animate
if (root.drawerOpen) {
root.enter.enabled = false;
root.visible = true;
root.position = 1;
root.enter.enabled = true;
}
__internal.completed = true;
contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
}
//END signal handlers
// this is as hidden as it can get here
property QtObject __internal: QtObject {
//here in order to not be accessible from outside
property bool completed: false
property SequentialAnimation positionResetAnim: SequentialAnimation {
id: positionResetAnim
property alias to: internalAnim.to
NumberAnimation {
id: internalAnim
target: root
to: drawerOpen ? 1 : 0
property: "position"
duration: (root.position)*Kirigami.Units.longDuration
}
ScriptAction {
script: {
root.drawerOpen = internalAnim.to !== 0;
}
}
}
readonly property Item statesItem: Item {
states: [
State {
when: root.collapsed
PropertyChanges {
target: root
implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(collapsedSize + leftPadding + rightPadding, Math.round(applicationWindow().width*0.8))
implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(collapsedSize + topPadding + bottomPadding, Math.round(applicationWindow().height*0.8))
}
},
State {
when: !root.collapsed
PropertyChanges {
target: root
implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8))
implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(contentHeight + topPadding + bottomPadding, Math.round(applicationWindow().height*0.4))
contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
}
}
]
transitions: Transition {
reversible: true
NumberAnimation {
properties: root.edge === Qt.TopEdge || root.edge === Qt.BottomEdge ? "implicitHeight" : "implicitWidth"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
}
@@ -0,0 +1,437 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief An overlay sheet that covers the current Page content.
*
* Its contents can be scrolled up or down, scrolling all the way up or
* all the way down, dismisses it.
* Use this for big, modal dialogs or information display, that can't be
* logically done as a new separate Page, even if potentially
* are taller than the screen space.
*
* Example usage:
* @code
* Kirigami.OverlaySheet {
* ColumnLayout { ... }
* }
* Kirigami.OverlaySheet {
* ListView { ... }
* }
* @endcode
*
* It needs a single element declared inside, do *not* override its contentItem
*
* @inherit QtQuick.Templates.Popup
*/
T.Popup {
id: root
Kirigami.OverlayZStacking.layer: Kirigami.OverlayZStacking.FullScreen
z: Kirigami.OverlayZStacking.z
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
//BEGIN Own Properties
/**
* @brief A title to be displayed in the header of this Sheet
*/
property string title
/**
* @brief This property sets the visibility of the close button in the top-right corner.
*
* default: `Only shown in desktop mode`
*
*/
property bool showCloseButton: !Kirigami.Settings.isMobile
/**
* @brief This property holds an optional item which will be used as the sheet's header,
* and will always be displayed.
*/
property Item header: Kirigami.Heading {
level: 2
text: root.title
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
// use tooltip for long text that is elided
T.ToolTip.visible: truncated && titleHoverHandler.hovered
T.ToolTip.text: root.title
HoverHandler {
id: titleHoverHandler
}
}
/**
* @brief An optional item which will be used as the sheet's footer,
* always kept on screen.
*/
property Item footer
default property alias flickableContentData: scrollView.contentData
//END Own Properties
//BEGIN Reimplemented Properties
QQC2.Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.3)
// the opacity of the item is changed internally by QQuickPopup on open/close
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
modal: true
dim: true
leftInset: -1
rightInset: -1
topInset: -1
bottomInset: -1
closePolicy: T.Popup.CloseOnEscape
x: parent ? Math.round(parent.width / 2 - width / 2) : 0
y: {
if (!parent) {
return 0;
}
const visualParentAdjust = sheetHandler.visualParent?.y ?? 0;
const wantedPosition = parent.height / 2 - implicitHeight / 2;
return Math.round(Math.max(visualParentAdjust, wantedPosition, Kirigami.Units.gridUnit * 3));
}
width: root.parent ? Math.min(root.parent.width, implicitWidth) : implicitWidth
implicitWidth: {
let width = parent?.width ?? 0;
if (!scrollView.itemForSizeHints) {
return width;
} else if (scrollView.itemForSizeHints.Layout.preferredWidth > 0) {
return Math.min(width, scrollView.itemForSizeHints.Layout.preferredWidth);
} else if (scrollView.itemForSizeHints.implicitWidth > 0) {
return Math.min(width, scrollView.itemForSizeHints.implicitWidth);
} else {
return width;
}
}
implicitHeight: {
let h = parent?.height ?? 0;
if (!scrollView.itemForSizeHints) {
return h - y;
} else if (scrollView.itemForSizeHints.Layout.preferredHeight > 0) {
h = scrollView.itemForSizeHints.Layout.preferredHeight;
} else if (scrollView.itemForSizeHints.implicitHeight > 0) {
h = scrollView.itemForSizeHints.implicitHeight + Kirigami.Units.largeSpacing * 2;
} else if (scrollView.itemForSizeHints instanceof Flickable && scrollView.itemForSizeHints.contentHeight > 0) {
h = scrollView.itemForSizeHints.contentHeight + Kirigami.Units.largeSpacing * 2;
} else {
h = scrollView.itemForSizeHints.height;
}
h += headerItem.implicitHeight + footerParent.implicitHeight + topPadding + bottomPadding;
return parent ? Math.min(h, parent.height - y) : h
}
//END Reimplemented Properties
//BEGIN Signal handlers
onVisibleChanged: {
const flickable = scrollView.contentItem;
flickable.contentY = flickable.originY - flickable.topMargin;
}
Component.onCompleted: {
Qt.callLater(() => {
if (!root.parent && typeof applicationWindow !== "undefined") {
root.parent = applicationWindow().overlay
}
});
}
Connections {
target: parent
function onVisibleChanged() {
if (!parent.visible) {
root.close();
}
}
}
//END Signal handlers
//BEGIN UI
contentItem: MouseArea {
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
Kirigami.Theme.inherit: false
property real scenePressY
property real lastY
property bool dragStarted
drag.filterChildren: true
DragHandler {
id: mouseDragBlocker
target: null
dragThreshold: 0
acceptedDevices: PointerDevice.Mouse
onActiveChanged: {
if (active) {
parent.dragStarted = false;
}
}
}
onPressed: mouse => {
scenePressY = mapToItem(null, mouse.x, mouse.y).y;
lastY = scenePressY;
dragStarted = false;
}
onPositionChanged: mouse => {
if (mouseDragBlocker.active) {
return;
}
const currentY = mapToItem(null, mouse.x, mouse.y).y;
if (dragStarted && currentY !== lastY) {
translation.y += currentY - lastY;
}
if (Math.abs(currentY - scenePressY) > Qt.styleHints.startDragDistance) {
dragStarted = true;
}
lastY = currentY;
}
onCanceled: restoreAnim.restart();
onReleased: mouse => {
if (mouseDragBlocker.active) {
return;
}
if (Math.abs(mapToItem(null, mouse.x, mouse.y).y - scenePressY) > Kirigami.Units.gridUnit * 5) {
root.close();
} else {
restoreAnim.restart();
}
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
spacing: 0
// Even though we're not actually using any shadows here,
// we're using a ShadowedRectangle instead of a regular
// rectangle because it allows fine-grained control over which
// corners to round, which we need here
Item {
id: headerItem
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
//Layout.margins: 1
visible: root.header || root.showCloseButton
implicitHeight: Math.max(headerParent.implicitHeight, closeIcon.height)// + Kirigami.Units.smallSpacing * 2
z: 2
Rectangle {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: Kirigami.Units.smallSpacing
}
width: Math.round(Kirigami.Units.gridUnit * 3)
height: Math.round(Kirigami.Units.gridUnit / 4)
radius: height
color: Kirigami.Theme.textColor
opacity: 0.4
visible: Kirigami.Settings.hasTransientTouchInput
}
Kirigami.Padding {
id: headerParent
readonly property real leadingPadding: Kirigami.Units.largeSpacing
readonly property real trailingPadding: (root.showCloseButton ? closeIcon.width : 0) + Kirigami.Units.smallSpacing
anchors.fill: parent
verticalPadding: Kirigami.Units.largeSpacing
leftPadding: root.mirrored ? trailingPadding : leadingPadding
rightPadding: root.mirrored ? leadingPadding : trailingPadding
contentItem: root.header
}
QQC2.ToolButton {
id: closeIcon
// We want to position the close button in the top-right
// corner if the header is very tall, but we want to
// vertically center it in a short header
readonly property bool tallHeader: parent.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing * 2)
Layout.alignment: tallHeader ? Qt.AlignRight | Qt.AlignTop : Qt.AlignRight | Qt.AlignVCenter
Layout.topMargin: tallHeader ? Kirigami.Units.largeSpacing : 0
anchors {
verticalCenter: !tallHeader ? undefined : parent.verticalCenter
right: parent.right
margins: Kirigami.Units.largeSpacing
}
z: 3
visible: root.showCloseButton
icon.name: closeIcon.hovered ? "window-close" : "window-close-symbolic"
text: qsTr("Close", "@action:button close dialog")
onClicked: root.close()
display: QQC2.AbstractButton.IconOnly
}
Kirigami.Separator {
anchors {
right: parent.right
left: parent.left
top: parent.bottom
}
visible: scrollView.T.ScrollBar.vertical.visible
}
}
// Here goes the main Sheet content
QQC2.ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
T.ScrollBar.horizontal.policy: T.ScrollBar.AlwaysOff
property bool initialized: false
property Item itemForSizeHints
// Important to not even access contentItem before it has been spontaneously created
contentWidth: initialized ? contentItem.width : width
contentHeight: itemForSizeHints?.implicitHeight ?? 0
onContentItemChanged: {
initialized = true;
const flickable = contentItem as Flickable;
flickable.boundsBehavior = Flickable.StopAtBounds;
if ((flickable instanceof ListView) || (flickable instanceof GridView)) {
itemForSizeHints = flickable;
return;
}
const content = flickable.contentItem;
content.childrenChanged.connect(() => {
for (const item of content.children) {
item.anchors.margins = Kirigami.Units.largeSpacing;
item.anchors.top = content.top;
item.anchors.left = content.left;
item.anchors.right = content.right;
}
itemForSizeHints = content.children?.[0] ?? null;
});
}
}
// Optional footer
Kirigami.Separator {
Layout.fillWidth: true
visible: footerParent.visible
}
Kirigami.Padding {
id: footerParent
Layout.fillWidth: true
padding: Kirigami.Units.smallSpacing
contentItem: root.footer
visible: contentItem !== null
}
}
Translate {
id: translation
}
MouseArea {
id: sheetHandler
readonly property Item visualParent: root.parent?.contentItem ?? root.parent
x: -root.x
y: -root.y
z: -1
width: visualParent?.width ?? 0
height: (visualParent?.height ?? 0) * 2
property var pressPos
onPressed: mouse => {
pressPos = mapToItem(null, mouse.x, mouse.y)
}
onReleased: mouse => {
// onClicked is emitted even if the mouse was dragged a lot, so we have to check the Manhattan length by hand
// https://en.wikipedia.org/wiki/Taxicab_geometry
let pos = mapToItem(null, mouse.x, mouse.y)
if (Math.abs(pos.x - pressPos.x) + Math.abs(pos.y - pressPos.y) < Qt.styleHints.startDragDistance) {
root.close();
}
}
NumberAnimation {
id: restoreAnim
target: translation
property: "y"
from: translation.y
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
Component.onCompleted: {
root.contentItem.parent.transform = translation
root.contentItem.parent.clip = false
}
}
}
//END UI
//BEGIN Transitions
enter: Transition {
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 0
to: 1
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
NumberAnimation {
target: translation
property: "y"
from: Kirigami.Units.gridUnit * 5
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
}
exit: Transition {
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 1
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
NumberAnimation {
target: translation
property: "y"
from: translation.y
to: translation.y >= 0 ? translation.y + Kirigami.Units.gridUnit * 5 : translation.y - Kirigami.Units.gridUnit * 5
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
}
//END Transitions
}
@@ -0,0 +1,13 @@
pragma Singleton
/*
* SPDX-FileCopyrightText: 2020 Carson Black
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import org.kde.kirigami as Kirigami
Kirigami.SizeGroup {
mode: Kirigami.SizeGroup.Height
}
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: button
icon.name: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic")
enabled: {
const pageStack = applicationWindow().pageStack;
if (pageStack.layers.depth > 1) {
return true;
}
if (pageStack.depth > 1) {
if (pageStack.currentIndex > 0) {
return true;
}
const view = pageStack.columnView;
if (LayoutMirroring.enabled) {
return view.contentWidth - view.width < view.contentX
} else {
return view.contentX > 0;
}
}
return false;
}
// The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
visible: {
const pageStack = applicationWindow().pageStack;
const showNavButtons = globalToolBar?.showNavigationButtons ?? Kirigami.ApplicationHeaderStyle.NoNavigationButtons;
return pageStack.layers.depth > 1 || (pageStack.contentItem.contentWidth > pageStack.width + Kirigami.Units.gridUnit && (showNavButtons & Kirigami.ApplicationHeaderStyle.ShowBackButton));
}
onClicked: {
applicationWindow().pageStack.goBack();
}
text: qsTr("Navigate Back")
display: QQC2.ToolButton.IconOnly
QQC2.ToolTip {
visible: button.hovered
text: button.text
delay: Kirigami.Units.toolTipDelay
timeout: 5000
y: button.height
}
}
@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
QtObject {
/**
* @brief This property holds the color of this border.
*/
property color color
/**
* @brief This property holds the width of this border.
*/
property real width
}
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: canvas
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
property int thickness: 2
property real position: drawer?.position ?? 0
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
opacity: 0.8
layer.enabled: true
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
Item {
id: iconRoot
anchors {
fill: parent
margins: Kirigami.Units.smallSpacing
}
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: (parent.height / 2 - canvas.thickness / 2) * canvas.position
}
antialiasing: canvas.position !== 0
transformOrigin: Item.Center
width: (1 - canvas.position) * height + canvas.position * Math.sqrt(2 * parent.width * parent.width)
height: canvas.thickness
color: canvas.color
rotation: 45 * canvas.position
}
Rectangle {
anchors.centerIn: parent
width: height
height: canvas.thickness
color: canvas.color
}
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: (parent.height / 2 - canvas.thickness / 2) * canvas.position
}
antialiasing: canvas.position !== 0
transformOrigin: Item.Center
width: (1 - canvas.position) * height + canvas.position * Math.sqrt(2 * parent.width * parent.width)
height: canvas.thickness
color: canvas.color
rotation: -45 * canvas.position
}
}
}
@@ -0,0 +1,208 @@
/*
* SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
MouseArea {
id: root
/*
* This property is used to set when the tooltip is visible.
* It exists because the text is changed while the tooltip is still visible.
*/
property bool displayToolTip: true
/**
* The drawer this handle will control
*/
// Should be KT.OverlayDrawer, but can't due to "Cyclic dependency"
property T.Drawer drawer
readonly property T.Overlay overlay: drawer.T.Overlay.overlay
// Above the Overlay when modal but below when non-modal and when the drawer is closed
// so that other overlays can be above it.
parent: overlay?.parent ?? null
z: overlay ? overlay.z + (drawer?.modal && drawer?.drawerOpen ? 1 : - 1) : 0
preventStealing: true
hoverEnabled: handleAnchor?.visible ?? false
QQC2.ToolButton {
anchors.centerIn: parent
width: parent.height - Kirigami.Units.smallSpacing * 1.5
height: parent.height - Kirigami.Units.smallSpacing * 1.5
visible: !Kirigami.Settings.tabletMode && !Kirigami.Settings.hasTransientTouchInput
Accessible.name: root.drawer.drawerOpen ? root.drawer.handleOpenToolTip : root.drawer.handleClosedToolTip
onClicked: {
root.displayToolTip = false;
Qt.callLater(() => {
root.drawer.drawerOpen = !root.drawer.drawerOpen;
})
}
Keys.onEscapePressed: {
if (root.drawer.closePolicy & T.Popup.CloseOnEscape) {
root.drawer.drawerOpen = false;
}
}
}
QQC2.ToolTip.visible: displayToolTip && containsMouse
QQC2.ToolTip.text: drawer.drawerOpen ? handleOpenToolTip : handleClosedToolTip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
property Item handleAnchor: {
if (typeof applicationWindow === "undefined") {
return null;
}
const window = applicationWindow();
const globalToolBar = window.pageStack?.globalToolBar;
if (!globalToolBar) {
return null;
}
return (drawer.edge === Qt.LeftEdge && !drawer.mirrored) || (drawer.edge === Qt.RightEdge && drawer.mirrored)
? globalToolBar.leftHandleAnchor
: globalToolBar.rightHandleAnchor;
}
property int startX
property int mappedStartX
enabled: drawer.handleVisible
onPressed: mouse => {
drawer.peeking = true;
startX = mouse.x;
mappedStartX = mapToItem(parent, startX, 0).x;
}
onPositionChanged: mouse => {
if (!pressed) {
return;
}
const pos = mapToItem(parent, mouse.x - startX, mouse.y);
switch (drawer.edge) {
case Qt.LeftEdge:
drawer.position = pos.x / drawer.contentItem.width;
break;
case Qt.RightEdge:
drawer.position = (drawer.parent.width - pos.x - width) / drawer.contentItem.width;
break;
default:
}
}
onReleased: mouse => {
drawer.peeking = false;
if (Math.abs(mapToItem(parent, mouse.x, 0).x - mappedStartX) < Qt.styleHints.startDragDistance) {
if (!drawer.drawerOpen) {
drawer.close();
}
drawer.drawerOpen = !drawer.drawerOpen;
}
}
onCanceled: {
drawer.peeking = false
}
x: {
switch (drawer.edge) {
case Qt.LeftEdge:
return drawer.background.width * drawer.position + Kirigami.Units.smallSpacing;
case Qt.RightEdge:
return parent.width - (drawer.background.width * drawer.position) - width - Kirigami.Units.smallSpacing;
default:
return 0;
}
}
Binding {
when: root.handleAnchor && root.anchors.bottom
target: root
property: "y"
value: root.handleAnchor ? root.handleAnchor.Kirigami.ScenePosition.y : 0
restoreMode: Binding.RestoreBinding
}
anchors {
bottom: handleAnchor ? undefined : parent.bottom
bottomMargin: {
if (typeof applicationWindow === "undefined") {
return undefined;
}
const window = applicationWindow();
let margin = Kirigami.Units.smallSpacing;
if (window.footer) {
margin = window.footer.height + Kirigami.Units.smallSpacing;
}
if (drawer.parent && drawer.height < drawer.parent.height) {
margin = drawer.parent.height - drawer.height - drawer.y + Kirigami.Units.smallSpacing;
}
if (!window || !window.pageStack ||
!window.pageStack.contentItem ||
!window.pageStack.contentItem.itemAt) {
return margin;
}
let item;
if (window.pageStack.layers.depth > 1) {
item = window.pageStack.layers.currentItem;
} else {
item = window.pageStack.contentItem.itemAt(window.pageStack.contentItem.contentX + x, 0);
}
// try to take the last item
if (!item) {
item = window.pageStack.lastItem;
}
let pageFooter = item && item.page ? item.page.footer : (item ? item.footer : undefined);
if (pageFooter && drawer.parent) {
margin = drawer.height < drawer.parent.height ? margin : margin + pageFooter.height
}
return margin;
}
Behavior on bottomMargin {
NumberAnimation {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
visible: drawer.enabled && (drawer.edge === Qt.LeftEdge || drawer.edge === Qt.RightEdge) && opacity > 0
width: handleAnchor?.visible ? handleAnchor.width : Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
height: handleAnchor?.visible ? handleAnchor.height : width
opacity: drawer.handleVisible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
transform: Translate {
x: root.drawer.handleVisible ? 0 : (root.drawer.edge === Qt.LeftEdge ? -root.width : root.width)
Behavior on x {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: !root.drawer.handleVisible ? Easing.OutQuad : Easing.InQuad
}
}
}
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: button
icon.name: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
enabled: applicationWindow().pageStack.currentIndex < applicationWindow().pageStack.depth-1
// The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
visible: {
const pageStack = applicationWindow().pageStack;
const showNavButtons = globalToolBar?.showNavigationButtons ?? Kirigami.ApplicationHeaderStyle.NoNavigationButtons;
return pageStack.layers.depth === 1 && pageStack.contentItem.contentWidth > pageStack.width + Kirigami.Units.gridUnit && (showNavButtons & Kirigami.ApplicationHeaderStyle.ShowForwardButton);
}
onClicked: applicationWindow().pageStack.goForward();
text: qsTr("Navigate Forward")
display: QQC2.ToolButton.IconOnly
QQC2.ToolTip {
visible: button.hovered
text: button.text
delay: Kirigami.Units.toolTipDelay
y: button.height
}
}
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
width: height
height: Kirigami.Units.iconSizes.smallMedium
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
opacity: 0.8
layer.enabled: true
Kirigami.Icon {
selected: drawer.handle.pressed
opacity: 1 - drawer.position
anchors.fill: parent
source: drawer.handleClosedIcon.name ? drawer.handleClosedIcon.name : drawer.handleClosedIcon.source
color: drawer.handleClosedIcon.color
}
Kirigami.Icon {
selected: drawer.handle.pressed
opacity: drawer.position
anchors.fill: parent
source: drawer.handleOpenIcon.name ? drawer.handleOpenIcon.name : drawer.handleOpenIcon.source
color: drawer.handleOpenIcon.color
}
}
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
/**
* @brief Group of icon properties.
*
* This is a subset of those used in QQC2, Kirigami.Action still needs the full one as needs 100% api compatibility
*/
QtObject {
/**
* @brief This property holds icon name.
*
* 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.
*/
property string name
/**
* @brief This property holds the icon source.
*
* The icon will be loaded as a regular image.
*
* @see QtQuick.Image::source
*/
property var source
/**
* @brief This property holds the icon tint color.
*
* The icon is tinted with the specified color, unless the color is set to "transparent".
*/
property color color: Qt.rgba(0, 0, 0, 0)
/**
* This property holds the width of the icon.
*/
property real width
/**
* This property holds the height of the icon.
*/
property real height
/**
* Bind this icon to all matching properties of a Controls icon group.
*
* This function automatically binds all properties to matching properties
* of a controls icon group, since we cannot just reuse the Controls icon
* group.
*
* To use it, you can assign the result to an IconPropertiesGroup, like so:
* `icon: icon.fromControlsIcon(control.icon)`.
*/
function fromControlsIcon(icon) {
name = Qt.binding(() => icon.name)
source = Qt.binding(() => icon.source)
color = Qt.binding(() => icon.color)
width = Qt.binding(() => icon.width)
height = Qt.binding(() => icon.height)
return this
}
}
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: canvas
width: height
height: Kirigami.Units.iconSizes.smallMedium
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
opacity: 0.8
layer.enabled: true
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
Item {
id: iconRoot
anchors {
fill: parent
margins: Kirigami.Units.smallSpacing
}
readonly property int thickness: 2
readonly property real drawerPosition: drawer ? drawer.position : 0
Rectangle {
anchors {
right: parent.right
top: parent.top
topMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
}
antialiasing: iconRoot.drawerPosition !== 0
transformOrigin: Item.Right
width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
height: iconRoot.thickness
color: canvas.color
rotation: -45 * iconRoot.drawerPosition
}
Rectangle {
anchors.centerIn: parent
width: parent.width - parent.width * iconRoot.drawerPosition
height: iconRoot.thickness
color: canvas.color
}
Rectangle {
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
}
antialiasing: iconRoot.drawerPosition !== 0
transformOrigin: Item.Right
width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
height: iconRoot.thickness
color: canvas.color
rotation: 45 * iconRoot.drawerPosition
}
}
}
@@ -0,0 +1,262 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief PassiveNotificationManager is meant to display small, passive and inline notifications in the app.
*
* It is used to show messages of limited importance that make sense only when
* the user is using the application and wouldn't be suited as a global
* system-wide notification.
*/
Item {
id: root
readonly property int maximumNotificationWidth: {
if (Kirigami.Settings.isMobile) {
return applicationWindow().width - Kirigami.Units.largeSpacing * 4
} else {
return Math.min(Kirigami.Units.gridUnit * 25, applicationWindow().width / 1.5)
}
}
readonly property int maximumNotificationCount: 4
function showNotification(message, timeout, actionText, callBack) {
if (!message) {
return;
}
let interval = 7000;
if (timeout === "short") {
interval = 4000;
} else if (timeout === "long") {
interval = 12000;
} else if (timeout > 0) {
interval = timeout;
}
// this wrapper is necessary because of Qt casting a function into an object
const callBackWrapperObj = callBackWrapper.createObject(listView, { callBack })
// set empty string & function for qml not to complain
notificationsModel.append({
text: message,
actionButtonText: actionText || "",
closeInterval: interval,
callBackWrapper: callBackWrapperObj
})
// remove the oldest notification if new notification count would exceed 3
if (notificationsModel.count === maximumNotificationCount) {
if (listView.itemAtIndex(0).hovered === true) {
hideNotification(1)
} else {
hideNotification()
}
}
}
/**
* @brief Remove a notification at specific index. By default, index is set to 0.
*/
function hideNotification(index = 0) {
if (index >= 0 && notificationsModel.count > index) {
const callBackWrapperObj = notificationsModel.get(index).callBackWrapper
if (callBackWrapperObj) {
callBackWrapperObj.destroy()
}
notificationsModel.remove(index)
}
}
// we have to set height to show more than one notification
height: Math.min(applicationWindow().height, Kirigami.Units.gridUnit * 10)
implicitHeight: listView.implicitHeight
implicitWidth: listView.implicitWidth
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
ListModel {
id: notificationsModel
}
ListView {
id: listView
anchors.fill: parent
anchors.bottomMargin: Kirigami.Units.largeSpacing
implicitWidth: root.maximumNotificationWidth
spacing: Kirigami.Units.smallSpacing
model: notificationsModel
verticalLayoutDirection: ListView.BottomToTop
keyNavigationEnabled: false
reuseItems: false // do not resue items, otherwise delegates do not hide themselves properly
focus: false
interactive: false
add: Transition {
id: addAnimation
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "y"
from: addAnimation.ViewTransition.destination.y - Kirigami.Units.gridUnit * 3
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
}
}
displaced: Transition {
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "y"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutCubic
}
NumberAnimation {
property: "opacity"
duration: 0
to: 1
}
}
}
remove: Transition {
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
NumberAnimation {
property: "y"
to: Kirigami.Units.gridUnit * 3
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
PropertyAction {
property: "transformOrigin"
value: Item.Bottom
}
PropertyAnimation {
property: "scale"
from: 1
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
}
}
delegate: QQC2.Control {
id: delegate
hoverEnabled: true
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: Math.min(implicitWidth, maximumNotificationWidth)
implicitHeight: {
// HACK: contentItem.implicitHeight needs to be updated manually for some reason
void contentItem.implicitHeight;
return Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding);
}
z: {
if (delegate.hovered) {
return 2;
} else if (delegate.index === 0) {
return 1;
} else {
return 0;
}
}
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
contentItem: RowLayout {
id: mainLayout
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
spacing: Kirigami.Units.mediumSpacing
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: eventPoint => hideNotification(index)
}
Timer {
id: timer
interval: model.closeInterval
running: !delegate.hovered
onTriggered: hideNotification(index)
}
QQC2.Label {
id: label
text: model.text
elide: Text.ElideRight
wrapMode: Text.Wrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
QQC2.Button {
id: actionButton
text: model.actionButtonText
visible: text.length > 0
Layout.alignment: Qt.AlignVCenter
onClicked: {
const callBack = model.callBackWrapper.callBack
hideNotification(index)
if (callBack && (typeof callBack === "function")) {
callBack();
}
}
}
}
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
shadow {
size: Kirigami.Units.gridUnit/2
color: Qt.rgba(0, 0, 0, 0.4)
yOffset: 2
}
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
opacity: 0.9
}
}
}
Component {
id: callBackWrapper
QtObject {
property var callBack
}
}
}
@@ -0,0 +1,11 @@
module org.kde.kirigami.templates.private
BackButton 1.0 BackButton.qml
BorderPropertiesGroup 1.0 BorderPropertiesGroup.qml
ContextIcon 1.0 ContextIcon.qml
DrawerHandle 1.0 DrawerHandle.qml
ForwardButton 1.0 ForwardButton.qml
GenericDrawerIcon 1.0 GenericDrawerIcon.qml
IconPropertiesGroup 1.0 IconPropertiesGroup.qml
MenuIcon 1.0 MenuIcon.qml
PassiveNotificationsManager 1.0 PassiveNotificationsManager.qml
@@ -0,0 +1,9 @@
module org.kde.kirigami.templates
AbstractApplicationHeader 2.2 AbstractApplicationHeader.qml
AbstractCard 2.4 AbstractCard.qml
singleton AppHeaderSizeGroup 2.2 SingletonHeaderSizeGroup.qml
Chip 2.19 Chip.qml
InlineMessage 2.4 InlineMessage.qml
OverlayDrawer 2.2 OverlayDrawer.qml
OverlaySheet 2.2 OverlaySheet.qml