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:
@@ -0,0 +1,64 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Build only C++ core, no QML modules
|
||||
add_library(Kirigami STATIC)
|
||||
add_library(KF6::Kirigami ALIAS Kirigami)
|
||||
|
||||
# Core C++ sources that don't require QML/QtQuick
|
||||
target_sources(Kirigami PRIVATE
|
||||
enums.h
|
||||
imagecolors.cpp
|
||||
imagecolors.h
|
||||
mnemonicattached.cpp
|
||||
mnemonicattached.h
|
||||
overlayzstackingattached.cpp
|
||||
overlayzstackingattached.h
|
||||
pagepool.cpp
|
||||
pagepool.h
|
||||
scenepositionattached.cpp
|
||||
scenepositionattached.h
|
||||
spellcheckattached.cpp
|
||||
spellcheckattached.h
|
||||
wheelhandler.cpp
|
||||
wheelhandler.h
|
||||
)
|
||||
|
||||
target_include_directories(Kirigami PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
|
||||
target_link_libraries(Kirigami PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
PRIVATE
|
||||
Qt6::Concurrent
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(Kirigami
|
||||
HEADER loggingcategory.h
|
||||
IDENTIFIER KirigamiLog
|
||||
CATEGORY_NAME kf.kirigami
|
||||
DESCRIPTION "Kirigami"
|
||||
DEFAULT_SEVERITY Warning
|
||||
EXPORT KIRIGAMI
|
||||
)
|
||||
|
||||
set_target_properties(Kirigami PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 6
|
||||
EXPORT_NAME "Kirigami"
|
||||
)
|
||||
|
||||
install(TARGETS Kirigami EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
install(EXPORT KirigamiTargets
|
||||
DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Kirigami
|
||||
FILE KF6KirigamiTargets.cmake
|
||||
NAMESPACE KF6
|
||||
)
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT KIRIGAMI
|
||||
FILE kirigami.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
<rules>
|
||||
<dependencies>
|
||||
<lib name="Kirigami">
|
||||
<depends>
|
||||
<bundled file="plugins/kf6/kirigami" />
|
||||
</depends>
|
||||
</lib>
|
||||
</dependencies>
|
||||
</rules>
|
||||
@@ -0,0 +1,4 @@
|
||||
#! /usr/bin/env bash
|
||||
$EXTRACT_TR_STRINGS `find . -name \*.qml -o -name \*.cpp` -o $podir/libkirigami6_qt.pot
|
||||
rm -f rc.cpp
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "actionhelper.h"
|
||||
|
||||
ActionHelper::ActionHelper(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString ActionHelper::iconName(const QIcon &icon) const
|
||||
{
|
||||
return icon.name();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ActionHelper::alternateShortcuts(QAction *action) const
|
||||
{
|
||||
if (!action || action->shortcuts().length() <= 1) {
|
||||
return {};
|
||||
} else {
|
||||
return action->shortcuts().mid(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAction>
|
||||
#include <QtQml/qqmlregistration.h>
|
||||
|
||||
/// \internal This is private API, do not use.
|
||||
class ActionHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
|
||||
public:
|
||||
explicit ActionHelper(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QList<QKeySequence> alternateShortcuts(QAction *action) const;
|
||||
Q_INVOKABLE QString iconName(const QIcon &icon) const;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+55
@@ -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
|
||||
}
|
||||
+174
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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
|
||||
}
|
||||
+157
@@ -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
|
||||
}
|
||||
+30
@@ -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
|
||||
}
|
||||
}
|
||||
+42
@@ -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
|
||||
}
|
||||
}
|
||||
+61
@@ -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 ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
@@ -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
|
||||
}
|
||||
}
|
||||
+19
@@ -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
|
||||
}
|
||||
}
|
||||
+33
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+69
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+262
@@ -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
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
|
||||
* SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
|
||||
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "copyhelper.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
|
||||
void CopyHelperPrivate::copyTextToClipboard(const QString &text)
|
||||
{
|
||||
qGuiApp->clipboard()->setText(text);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
|
||||
* SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
|
||||
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef COPYHELPER_H
|
||||
#define COPYHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
class CopyHelperPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
public:
|
||||
Q_INVOKABLE void copyTextToClipboard(const QString &text);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
add_library(KirigamiDelegates)
|
||||
ecm_add_qml_module(KirigamiDelegates URI "org.kde.kirigami.delegates"
|
||||
GENERATE_PLUGIN_SOURCE
|
||||
INSTALLED_PLUGIN_TARGET KF6KirigamiDelegates
|
||||
DEPENDENCIES QtQuick org.kde.kirigami.platform org.kde.kirigami.primitives
|
||||
)
|
||||
|
||||
ecm_target_qml_sources(KirigamiDelegates SOURCES
|
||||
IconTitleSubtitle.qml
|
||||
TitleSubtitle.qml
|
||||
|
||||
SubtitleDelegate.qml
|
||||
CheckSubtitleDelegate.qml
|
||||
RadioSubtitleDelegate.qml
|
||||
SwitchSubtitleDelegate.qml
|
||||
)
|
||||
|
||||
set_target_properties(KirigamiDelegates PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 6
|
||||
EXPORT_NAME "KirigamiDelegates"
|
||||
)
|
||||
|
||||
ecm_finalize_qml_module(KirigamiDelegates EXPORT KirigamiTargets)
|
||||
|
||||
install(TARGETS KirigamiDelegates EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami.platform as Platform
|
||||
|
||||
/**
|
||||
* A convenience wrapper combining QtQuick Controls CheckDelegate and IconTitleSubtitle
|
||||
*
|
||||
* This is an intentionally minimal wrapper that replaces the CheckDelegate's
|
||||
* contentItem with an IconTitleSubtitle and adds a subtitle property.
|
||||
*
|
||||
* If you wish to customize the layout further, create your own `CheckDelegate`
|
||||
* subclass with the `contentItem:` property set to the content of your choice.
|
||||
* This can include `IconTitleSubtitle` inside a Layout, for example.
|
||||
*
|
||||
* \note If you don't need a subtitle, use `CheckDelegate` directly.
|
||||
*
|
||||
* \sa Kirigami::Delegates::TitleSubtitle
|
||||
* \sa Kirigami::Delegates::IconTitleSubtitle
|
||||
*/
|
||||
QQC2.CheckDelegate {
|
||||
id: delegate
|
||||
|
||||
// Please see the developer note in ItemDelegate
|
||||
|
||||
/**
|
||||
* The subtitle to display.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
|
||||
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
|
||||
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
|
||||
|
||||
contentItem: IconTitleSubtitle {
|
||||
icon: icon.fromControlsIcon(delegate.icon)
|
||||
title: delegate.text
|
||||
subtitle: delegate.subtitle
|
||||
selected: delegate.highlighted || delegate.down
|
||||
font: delegate.font
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import org.kde.kirigami.platform as Platform
|
||||
import org.kde.kirigami.primitives as Primitives
|
||||
import org.kde.kirigami.templates.private as KTP
|
||||
|
||||
/**
|
||||
* A simple item containing an icon, title and subtitle.
|
||||
*
|
||||
* This is an extension of TitleSubtitle that adds an icon to the side.
|
||||
* It is intended as a contentItem for ItemDelegate and related controls.
|
||||
*
|
||||
* When using it as a contentItem, make sure to bind the appropriate properties
|
||||
* to those of the Control. Prefer binding to the Control's properties over
|
||||
* setting the properties directly, as the Control's properties may affect other
|
||||
* things like setting accessible names.
|
||||
*
|
||||
* This (and TitleSubtitle) can be combined with other controls in a layout to
|
||||
* create complex content items for controls.
|
||||
*
|
||||
* Example usage creating a CheckDelegate with an extra button on the side:
|
||||
*
|
||||
* ```qml
|
||||
* CheckDelegate {
|
||||
* id: delegate
|
||||
*
|
||||
* text: "Example"
|
||||
* icon.name: "document-new"
|
||||
*
|
||||
* contentItem: RowLayout {
|
||||
* spacing: Kirigami.Units.smallSpacing
|
||||
*
|
||||
* Kirigami.IconTitleSubtitle {
|
||||
* Layout.fillWidth: true
|
||||
*
|
||||
* icon: icon.fromControlsIcon(delegate.icon)
|
||||
* title: delegate.text
|
||||
* selected: delegate.highlighted || delegate.down
|
||||
* font: delegate.font
|
||||
* }
|
||||
*
|
||||
* Button {
|
||||
* icon.name: "document-open"
|
||||
* text: "Extra Action"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* \sa Kirigami::Delegates::TitleSubtitle
|
||||
* \sa Kirigami::Delegates::ItemDelegate
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::title
|
||||
*/
|
||||
required property string title
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::subtitle
|
||||
*/
|
||||
property alias subtitle: titleSubtitle.subtitle
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::color
|
||||
*/
|
||||
property alias color: titleSubtitle.color
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::subtitleColor
|
||||
*/
|
||||
property alias subtitleColor: titleSubtitle.subtitleColor
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::font
|
||||
*/
|
||||
property alias font: titleSubtitle.font
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::subtitleFont
|
||||
*/
|
||||
property alias subtitleFont: titleSubtitle.subtitleFont
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::reserveSpaceForSubtitle
|
||||
*/
|
||||
property alias reserveSpaceForSubtitle: titleSubtitle.reserveSpaceForSubtitle
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::selected
|
||||
*/
|
||||
property alias selected: titleSubtitle.selected
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::elide
|
||||
*/
|
||||
property alias elide: titleSubtitle.elide
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::wrapMode
|
||||
*/
|
||||
property alias wrapMode: titleSubtitle.wrapMode
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::truncated
|
||||
*/
|
||||
property alias truncated: titleSubtitle.truncated
|
||||
|
||||
/**
|
||||
* Grouped property for icon properties.
|
||||
*
|
||||
* \note By default, IconTitleSubtitle will reserve the space for the icon,
|
||||
* even if it is not set. To remove that space, set `icon.width` to 0.
|
||||
*/
|
||||
property KTP.IconPropertiesGroup icon: KTP.IconPropertiesGroup {
|
||||
width: titleSubtitle.subtitleVisible ? Platform.Units.iconSizes.medium : Platform.Units.iconSizes.smallMedium
|
||||
height: width
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::linkActivated
|
||||
*/
|
||||
signal linkActivated(string link)
|
||||
|
||||
/**
|
||||
* @copydoc Kirigami::TitleSubtitle::linkHovered
|
||||
*/
|
||||
signal linkHovered(string link)
|
||||
|
||||
implicitWidth: iconItem.implicitWidth + titleSubtitle.anchors.leftMargin + titleSubtitle.implicitWidth
|
||||
implicitHeight: Math.max(iconItem.implicitHeight, titleSubtitle.implicitHeight)
|
||||
|
||||
Primitives.Icon {
|
||||
id: iconItem
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
source: root.icon.name.length > 0 ? root.icon.name : root.icon.source
|
||||
implicitWidth: root.icon.width
|
||||
implicitHeight: root.icon.height
|
||||
selected: root.selected
|
||||
color: root.icon.color
|
||||
}
|
||||
|
||||
TitleSubtitle {
|
||||
id: titleSubtitle
|
||||
|
||||
anchors {
|
||||
left: iconItem.right
|
||||
leftMargin: root.icon.width > 0 ? Platform.Units.mediumSpacing : 0
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
title: root.title
|
||||
|
||||
onLinkActivated: link => root.linkActivated(link)
|
||||
onLinkHovered: link => root.linkHovered(link)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Kirigami Delegates Module
|
||||
|
||||
This module contains custom delegates and types used to build delegates from.
|
||||
|
||||
# What goes here
|
||||
|
||||
The following criteria should be used to determine if a type belongs here:
|
||||
|
||||
- Custom delegate types.
|
||||
- Types used to build custom delegate types.
|
||||
- Types are allowed to depend on QtQuick Controls.
|
||||
- Types are only allowed to depend on the Platform and Primitives submodules.
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami.platform as Platform
|
||||
|
||||
/**
|
||||
* A convenience wrapper combining QtQuick Controls RadioDelegate and IconTitleSubtitle
|
||||
*
|
||||
* This is an intentionally minimal wrapper that replaces the RadioDelegate's
|
||||
* contentItem with an IconTitleSubtitle and adds a subtitle property.
|
||||
*
|
||||
* If you wish to customize the layout further, create your own `RadioDelegate`
|
||||
* subclass with the `contentItem:` property set to the content of your choice.
|
||||
* This can include `IconTitleSubtitle` inside a Layout, for example.
|
||||
*
|
||||
* \note If you don't need a subtitle, use `RadioDelegate` directly.
|
||||
*
|
||||
* \sa Kirigami::Delegates::TitleSubtitle
|
||||
* \sa Kirigami::Delegates::IconTitleSubtitle
|
||||
*/
|
||||
QQC2.RadioDelegate {
|
||||
id: delegate
|
||||
|
||||
// Please see the developer note in ItemDelegate
|
||||
|
||||
/**
|
||||
* The subtitle to display.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
|
||||
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
|
||||
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
|
||||
|
||||
contentItem: IconTitleSubtitle {
|
||||
icon: icon.fromControlsIcon(delegate.icon)
|
||||
title: delegate.text
|
||||
subtitle: delegate.subtitle
|
||||
selected: delegate.highlighted || delegate.down
|
||||
font: delegate.font
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami.platform as Platform
|
||||
|
||||
/**
|
||||
* A convenience wrapper combining QtQuick Controls ItemDelegate and IconTitleSubtitle
|
||||
*
|
||||
* This is an intentionally minimal wrapper that replaces the ItemDelegate's
|
||||
* contentItem with an IconTitleSubtitle and adds a subtitle property.
|
||||
*
|
||||
* If you wish to customize the layout further, create your own `ItemDelegate`
|
||||
* subclass with the `contentItem:` property set to the content of your choice.
|
||||
* This can include `IconTitleSubtitle` inside a Layout, for example.
|
||||
*
|
||||
* \note If you don't need a subtitle, use `ItemDelegate` directly.
|
||||
*
|
||||
* \sa Kirigami::Delegates::TitleSubtitle
|
||||
* \sa Kirigami::Delegates::IconTitleSubtitle
|
||||
*/
|
||||
QQC2.ItemDelegate {
|
||||
id: delegate
|
||||
|
||||
// Developer note: This is intentional kept incredibly minimal as we want to
|
||||
// reuse as much of upstream ItemDelegate as possible, the only extra thing
|
||||
// being the subtitle property. Should that ever become an upstream feature,
|
||||
// these controls will be removed in favour of using upstream's implementation
|
||||
// directly.
|
||||
|
||||
/**
|
||||
* The subtitle to display.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
|
||||
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
|
||||
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
|
||||
|
||||
contentItem: IconTitleSubtitle {
|
||||
icon: icon.fromControlsIcon(delegate.icon)
|
||||
title: delegate.text
|
||||
subtitle: delegate.subtitle
|
||||
selected: delegate.highlighted || delegate.down
|
||||
font: delegate.font
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami.platform as Platform
|
||||
|
||||
/**
|
||||
* A convenience wrapper combining QtQuick Controls SwitchDelegate and IconTitleSubtitle
|
||||
*
|
||||
* This is an intentionally minimal wrapper that replaces the SwitchDelegate's
|
||||
* contentItem with an IconTitleSubtitle and adds a subtitle property.
|
||||
*
|
||||
* If you wish to customize the layout further, create your own `SwitchDelegate`
|
||||
* subclass with the `contentItem:` property set to the content of your choice.
|
||||
* This can include `IconTitleSubtitle` inside a Layout, for example.
|
||||
*
|
||||
* \note If you don't need a subtitle, use `SwitchDelegate` directly.
|
||||
*
|
||||
* \sa Kirigami::Delegates::TitleSubtitle
|
||||
* \sa Kirigami::Delegates::IconTitleSubtitle
|
||||
*/
|
||||
QQC2.SwitchDelegate {
|
||||
id: delegate
|
||||
|
||||
// Please see the developer note in ItemDelegate
|
||||
|
||||
/**
|
||||
* The subtitle to display.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
|
||||
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
|
||||
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
|
||||
|
||||
contentItem: IconTitleSubtitle {
|
||||
icon: icon.fromControlsIcon(delegate.icon)
|
||||
title: delegate.text
|
||||
subtitle: delegate.subtitle
|
||||
selected: delegate.highlighted || delegate.down
|
||||
font: delegate.font
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
|
||||
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import org.kde.kirigami.platform as Platform
|
||||
|
||||
/**
|
||||
* A simple item containing a title and subtitle label.
|
||||
*
|
||||
* This is mainly intended as a replacement for a list delegate content item,
|
||||
* but can be used as a replacement for other content items as well.
|
||||
*
|
||||
* When using it as a contentItem, make sure to bind the appropriate properties
|
||||
* to those of the Control. Prefer binding to the Control's properties over
|
||||
* setting the properties directly, as the Control's properties may affect other
|
||||
* things like setting accessible names.
|
||||
*
|
||||
* Example usage as contentItem of an ItemDelegate:
|
||||
*
|
||||
* ```qml
|
||||
* ItemDelegate {
|
||||
* id: delegate
|
||||
*
|
||||
* text: "Example"
|
||||
*
|
||||
* contentItem: Kirigami.TitleSubtitle {
|
||||
* title: delegate.text
|
||||
* subtitle: "This is an example."
|
||||
* font: delegate.font
|
||||
* selected: delegate.highlighted || delegate.down
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* \sa Kirigami::Delegates::IconTitleSubtitle
|
||||
* \sa Kirigami::Delegates::ItemDelegate
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* The title to display.
|
||||
*/
|
||||
required property string title
|
||||
/**
|
||||
* The subtitle to display.
|
||||
*/
|
||||
property string subtitle
|
||||
/**
|
||||
* The color to use for the title.
|
||||
*
|
||||
* By default this is `Kirigami.Theme.textColor` unless `selected` is true
|
||||
* in which case this is `Kirigami.Theme.highlightedTextColor`.
|
||||
*/
|
||||
property color color: selected ? Platform.Theme.highlightedTextColor : Platform.Theme.textColor
|
||||
/**
|
||||
* The color to use for the subtitle.
|
||||
*
|
||||
* By default this is `color` mixed with the background color.
|
||||
*/
|
||||
property color subtitleColor: selected
|
||||
? Platform.Theme.highlightedTextColor
|
||||
: Platform.ColorUtils.linearInterpolation(color, Platform.Theme.backgroundColor, 0.3)
|
||||
/**
|
||||
* The font used to display the title.
|
||||
*/
|
||||
property font font: Platform.Theme.defaultFont
|
||||
/**
|
||||
* The font used to display the subtitle.
|
||||
*/
|
||||
property font subtitleFont: Platform.Theme.smallFont
|
||||
/**
|
||||
* The text elision mode used for both the title and subtitle.
|
||||
*/
|
||||
property int elide: Text.ElideRight
|
||||
/**
|
||||
* The text wrap mode used for both the title and subtitle.
|
||||
*/
|
||||
property int wrapMode: Text.NoWrap
|
||||
/**
|
||||
* Make the implicit height use the subtitle's height even if no subtitle is set.
|
||||
*/
|
||||
property bool reserveSpaceForSubtitle: false
|
||||
/**
|
||||
* Should this item be displayed in a selected style?
|
||||
*/
|
||||
property bool selected: false
|
||||
/**
|
||||
* Is the subtitle visible?
|
||||
*/
|
||||
// Note: Don't rely on subtitleItem.visible because visibility is an
|
||||
// implicitly propagated property, and we don't wanna re-layout on
|
||||
// hide/show events. Copy-paste its bound expression instead.
|
||||
readonly property bool subtitleVisible: subtitle.length > 0 || reserveSpaceForSubtitle
|
||||
/**
|
||||
* Is the title or subtitle truncated?
|
||||
*/
|
||||
readonly property bool truncated: labelItem.truncated || subtitleItem.truncated
|
||||
|
||||
/**
|
||||
* @brief Emitted when the user clicks on a link embedded in the text of the title or subtitle.
|
||||
*/
|
||||
signal linkActivated(string link)
|
||||
|
||||
/**
|
||||
* @brief Emitted when the user hovers on a link embedded in the text of the title or subtitle.
|
||||
*/
|
||||
signal linkHovered(string link)
|
||||
|
||||
implicitWidth: Math.max(labelItem.implicitWidth, subtitleItem.implicitWidth)
|
||||
implicitHeight: labelItem.implicitHeight + (subtitleVisible ? subtitleItem.implicitHeight : 0)
|
||||
|
||||
Text {
|
||||
id: labelItem
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Switch off here as this is expected to be set in the base component.
|
||||
Accessible.ignored: true
|
||||
|
||||
text: root.title
|
||||
color: root.color
|
||||
font: root.font
|
||||
elide: root.elide
|
||||
wrapMode: root.wrapMode
|
||||
|
||||
onLinkActivated: link => root.linkActivated(link)
|
||||
onLinkHovered: link => root.linkHovered(link)
|
||||
|
||||
// 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
|
||||
|
||||
// Note: Can't do this through ordinary bindings as the order between
|
||||
// binding evaluation is not defined which leads to incorrect sizing or
|
||||
// the QML engine complaining about not being able to anchor to null items.
|
||||
states: State {
|
||||
// Note: Same thing about visibility as in subtitleVisible above.
|
||||
when: root.subtitle.length > 0
|
||||
AnchorChanges {
|
||||
target: labelItem
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.bottom: subtitleItem.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: subtitleItem
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
text: root.subtitle
|
||||
color: root.subtitleColor
|
||||
font: root.subtitleFont
|
||||
elide: root.elide
|
||||
wrapMode: root.wrapMode
|
||||
|
||||
visible: text.length > 0
|
||||
|
||||
onLinkActivated: link => root.linkActivated(link)
|
||||
onLinkHovered: link => root.linkHovered(link)
|
||||
|
||||
// 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
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
add_library(KirigamiDialogs)
|
||||
ecm_add_qml_module(KirigamiDialogs URI "org.kde.kirigami.dialogs"
|
||||
VERSION 2.0
|
||||
GENERATE_PLUGIN_SOURCE
|
||||
INSTALLED_PLUGIN_TARGET KF6KirigamiDialogsplugin
|
||||
DEPENDENCIES QtQuick org.kde.kirigami.platform
|
||||
)
|
||||
|
||||
|
||||
|
||||
ecm_target_qml_sources(KirigamiDialogs SOURCES
|
||||
Dialog.qml
|
||||
MenuDialog.qml
|
||||
PromptDialog.qml
|
||||
SearchDialog.qml
|
||||
)
|
||||
|
||||
set_target_properties(KirigamiDialogs PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 6
|
||||
EXPORT_NAME "KirigamiDialogs"
|
||||
)
|
||||
|
||||
target_include_directories(KirigamiDialogs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
target_link_libraries(KirigamiDialogs PRIVATE Qt6::Quick KirigamiPlatform)
|
||||
|
||||
ecm_finalize_qml_module(KirigamiDialogs EXPORT KirigamiTargets)
|
||||
|
||||
install(TARGETS KirigamiDialogs EXPORT KirigamiTargets ${KF_INSTALL_DEFAULT_ARGUMENTS})
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQml
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates as T
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* @brief Popup dialog that is used for short tasks and user interaction.
|
||||
*
|
||||
* Dialog consists of three components: the header, the content,
|
||||
* and the footer.
|
||||
*
|
||||
* By default, the header is a heading with text specified by the
|
||||
* `title` property.
|
||||
*
|
||||
* By default, the footer consists of a row of buttons specified by
|
||||
* the `standardButtons` and `customFooterActions` properties.
|
||||
*
|
||||
* The `implicitHeight` and `implicitWidth` of the dialog contentItem is
|
||||
* the primary hint used for the dialog size. The dialog will be the
|
||||
* minimum size required for the header, footer and content unless
|
||||
* it is larger than `maximumHeight` and `maximumWidth`. Use
|
||||
* `preferredHeight` and `preferredWidth` in order to manually specify
|
||||
* a size for the dialog.
|
||||
*
|
||||
* If the content height exceeds the maximum height of the dialog, the
|
||||
* dialog's contents will become scrollable.
|
||||
*
|
||||
* If the contentItem is a <b>ListView</b>, the dialog will take care of the
|
||||
* necessary scrollbars and scrolling behaviour. Do <b>not</b> attempt
|
||||
* to nest ListViews (it must be the top level item), as the scrolling
|
||||
* behaviour will not be handled. Use ListView's `header` and `footer` instead.
|
||||
*
|
||||
* Example for a selection dialog:
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import QtQuick.Layouts
|
||||
* import QtQuick.Controls as QQC2
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.Dialog {
|
||||
* title: i18n("Dialog")
|
||||
* padding: 0
|
||||
* preferredWidth: Kirigami.Units.gridUnit * 16
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
*
|
||||
* onAccepted: console.log("OK button pressed")
|
||||
* onRejected: console.log("Rejected")
|
||||
*
|
||||
* ColumnLayout {
|
||||
* spacing: 0
|
||||
* Repeater {
|
||||
* model: 5
|
||||
* delegate: QQC2.CheckDelegate {
|
||||
* topPadding: Kirigami.Units.smallSpacing * 2
|
||||
* bottomPadding: Kirigami.Units.smallSpacing * 2
|
||||
* Layout.fillWidth: true
|
||||
* text: modelData
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Example with scrolling (ListView scrolling behaviour is handled by the Dialog):
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import QtQuick.Layouts
|
||||
* import QtQuick.Controls as QQC2
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.Dialog {
|
||||
* id: scrollableDialog
|
||||
* title: i18n("Select Number")
|
||||
*
|
||||
* ListView {
|
||||
* id: listView
|
||||
* // hints for the dialog dimensions
|
||||
* implicitWidth: Kirigami.Units.gridUnit * 16
|
||||
* implicitHeight: Kirigami.Units.gridUnit * 16
|
||||
*
|
||||
* model: 100
|
||||
* delegate: QQC2.RadioDelegate {
|
||||
* topPadding: Kirigami.Units.smallSpacing * 2
|
||||
* bottomPadding: Kirigami.Units.smallSpacing * 2
|
||||
* implicitWidth: listView.width
|
||||
* text: modelData
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* There are also sub-components of the Dialog that target specific usecases,
|
||||
* and can reduce boilerplate code if used:
|
||||
*
|
||||
* @see PromptDialog
|
||||
* @see MenuDialog
|
||||
*
|
||||
* @inherit QtQuick.QtObject
|
||||
*/
|
||||
T.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief This property holds the dialog's contents; includes Items and QtObjects.
|
||||
* @property list<QtObject> dialogData
|
||||
*/
|
||||
default property alias dialogData: contentControl.contentData
|
||||
|
||||
/**
|
||||
* @brief This property holds the content items of the dialog.
|
||||
*
|
||||
* The initial height and width of the dialog is calculated from the
|
||||
* `implicitWidth` and `implicitHeight` of the content.
|
||||
*
|
||||
* @property list<Item> dialogChildren
|
||||
*/
|
||||
property alias dialogChildren: contentControl.contentChildren
|
||||
|
||||
/**
|
||||
* @brief This property sets the absolute maximum height the dialog can have.
|
||||
*
|
||||
* The height restriction is solely applied on the content, so if the
|
||||
* maximum height given is not larger than the height of the header and
|
||||
* footer, it will be ignored.
|
||||
*
|
||||
* This is the window height, subtracted by largeSpacing on both the top
|
||||
* and bottom.
|
||||
*/
|
||||
readonly property real absoluteMaximumHeight: parent ? (parent.height - Kirigami.Units.largeSpacing * 2) : Infinity
|
||||
|
||||
/**
|
||||
* @brief This property holds the absolute maximum width the dialog can have.
|
||||
*
|
||||
* By default, it is the window width, subtracted by largeSpacing on both
|
||||
* the top and bottom.
|
||||
*/
|
||||
readonly property real absoluteMaximumWidth: parent ? (parent.width - Kirigami.Units.largeSpacing * 2) : Infinity
|
||||
|
||||
readonly property real __borderWidth: 1
|
||||
|
||||
/**
|
||||
* @brief This property holds the maximum height the dialog can have
|
||||
* (including the header and footer).
|
||||
*
|
||||
* The height restriction is solely enforced on the content, so if the
|
||||
* maximum height given is not larger than the height of the header and
|
||||
* footer, it will be ignored.
|
||||
*
|
||||
* By default, this is `absoluteMaximumHeight`.
|
||||
*/
|
||||
property real maximumHeight: absoluteMaximumHeight
|
||||
|
||||
/**
|
||||
* @brief This property holds the maximum width the dialog can have.
|
||||
*
|
||||
* By default, this is `absoluteMaximumWidth`.
|
||||
*/
|
||||
property real maximumWidth: absoluteMaximumWidth
|
||||
|
||||
/**
|
||||
* @brief This property holds the preferred height of the dialog.
|
||||
*
|
||||
* The content will receive a hint for how tall it should be to have
|
||||
* the dialog to be this height.
|
||||
*
|
||||
* If the content, header or footer require more space, then the height
|
||||
* of the dialog will expand to the necessary amount of space.
|
||||
*/
|
||||
property real preferredHeight: -1
|
||||
|
||||
/**
|
||||
* @brief This property holds the preferred width of the dialog.
|
||||
*
|
||||
* The content will receive a hint for how wide it should be to have
|
||||
* the dialog be this wide.
|
||||
*
|
||||
* If the content, header or footer require more space, then the width
|
||||
* of the dialog will expand to the necessary amount of space.
|
||||
*/
|
||||
property real preferredWidth: -1
|
||||
|
||||
|
||||
/**
|
||||
* @brief This property holds the component to the left of the footer buttons.
|
||||
*/
|
||||
property Component footerLeadingComponent
|
||||
|
||||
/**
|
||||
* @brief his property holds the component to the right of the footer buttons.
|
||||
*/
|
||||
property Component footerTrailingComponent
|
||||
|
||||
/**
|
||||
* @brief This property sets whether to show the close button in the header.
|
||||
*/
|
||||
property bool showCloseButton: true
|
||||
|
||||
/**
|
||||
* @brief This property sets whether the footer button style should be flat.
|
||||
*/
|
||||
property bool flatFooterButtons: false
|
||||
|
||||
/**
|
||||
* @brief This property holds the custom actions displayed in the footer.
|
||||
*
|
||||
* Example usage:
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.PromptDialog {
|
||||
* id: dialog
|
||||
* title: i18n("Confirm Playback")
|
||||
* subtitle: i18n("Are you sure you want to play this song? It's really loud!")
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.Cancel
|
||||
* customFooterActions: [
|
||||
* Kirigami.Action {
|
||||
* text: i18n("Play")
|
||||
* icon.name: "media-playback-start"
|
||||
* onTriggered: {
|
||||
* //...
|
||||
* dialog.close();
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see org::kde::kirigami::Action
|
||||
*/
|
||||
property list<T.Action> customFooterActions
|
||||
|
||||
// DialogButtonBox should NOT contain invisible buttons, because in Qt 6
|
||||
// ListView preserves space even for invisible items.
|
||||
readonly property list<T.Action> __visibleCustomFooterActions: customFooterActions
|
||||
.filter(action => !(action instanceof Kirigami.Action) || action?.visible)
|
||||
|
||||
function standardButton(button): T.AbstractButton {
|
||||
// in case a footer is redefined
|
||||
if (footer instanceof T.DialogButtonBox) {
|
||||
return footer.standardButton(button);
|
||||
} else if (footer === footerToolBar) {
|
||||
return dialogButtonBox.standardButton(button);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function customFooterButton(action: T.Action): T.AbstractButton {
|
||||
if (!action) {
|
||||
// Even if there's a null object in the list of actions, we should
|
||||
// not return a button for it.
|
||||
return null;
|
||||
}
|
||||
const index = __visibleCustomFooterActions.indexOf(action);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return customFooterButtons.itemAt(index) as T.AbstractButton;
|
||||
}
|
||||
|
||||
z: Kirigami.OverlayZStacking.z
|
||||
|
||||
// calculate dimensions and in case footer is wider than content, use that
|
||||
implicitWidth: Math.max(contentItem.implicitWidth, footerToolBar.implicitWidth, heading.implicitWidth) + leftPadding + rightPadding // maximum width enforced from our content (one source of truth) to avoid binding loops
|
||||
implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
|
||||
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
|
||||
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0);
|
||||
|
||||
// misc. dialog settings
|
||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnReleaseOutside
|
||||
modal: true
|
||||
clip: false
|
||||
padding: 0
|
||||
horizontalPadding: __borderWidth + padding
|
||||
|
||||
// determine parent so that popup knows which window to popup in
|
||||
// we want to open the dialog in the center of the window, if possible
|
||||
Component.onCompleted: {
|
||||
if (typeof applicationWindow !== "undefined") {
|
||||
parent = applicationWindow().overlay;
|
||||
}
|
||||
}
|
||||
|
||||
// center dialog
|
||||
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||
y: parent ? Math.round((parent.height - height) / 2) + Kirigami.Units.gridUnit * 2 * (1 - opacity) : 0 // move animation
|
||||
|
||||
// dialog enter and exit transitions
|
||||
enter: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 1; to: 0; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
|
||||
// black background, fades in and out
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dialog view background
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
id: rect
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
shadow {
|
||||
size: radius * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
yOffset: 1
|
||||
}
|
||||
|
||||
border {
|
||||
width: root.__borderWidth
|
||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast);
|
||||
}
|
||||
}
|
||||
|
||||
// dialog content
|
||||
contentItem: QQC2.ScrollView {
|
||||
id: contentControl
|
||||
|
||||
// ensure view colour scheme, and background color
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
// height of everything else in the dialog other than the content
|
||||
property real otherHeights: (root.header?.height ?? 0) + (root.footer?.height ?? 0) + root.topPadding + root.bottomPadding;
|
||||
|
||||
property real calculatedMaximumWidth: Math.min(root.absoluteMaximumWidth, root.maximumWidth) - root.leftPadding - root.rightPadding
|
||||
property real calculatedMaximumHeight: Math.min(root.absoluteMaximumHeight, root.maximumHeight) - root.topPadding - root.bottomPadding
|
||||
property real calculatedImplicitWidth: (contentChildren.length === 1 && contentChildren[0].implicitWidth > 0 ? contentChildren[0].implicitWidth : contentItem.implicitWidth) + leftPadding + rightPadding
|
||||
property real calculatedImplicitHeight: (contentChildren.length === 1 && contentChildren[0].implicitHeight > 0? contentChildren[0].implicitHeight: contentItem.implicitHeight) + topPadding + bottomPadding
|
||||
|
||||
onContentItemChanged: {
|
||||
const contentFlickable = contentItem as Flickable;
|
||||
if (contentFlickable) {
|
||||
/*
|
||||
Why this is necessary? A Flickable mainItem syncs its size with the contents only on startup,
|
||||
and if the contents can change their size dinamically afterwards (wrapping text does that),
|
||||
the contentsize will be wrong see BUG 477257.
|
||||
|
||||
We also don't do this declaratively but only we are sure a contentItem is declared/created as just
|
||||
accessing the property would create an internal Flickable, making it impossible to assign custom
|
||||
flickables/listviews to the Dialog.
|
||||
*/
|
||||
contentFlickable.contentHeight = Qt.binding(() => calculatedImplicitHeight);
|
||||
|
||||
contentFlickable.clip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// how do we deal with the scrollbar width?
|
||||
// - case 1: the dialog itself has the preferredWidth set
|
||||
// -> we hint a width to the content so it shrinks to give space to the scrollbar
|
||||
// - case 2: preferredWidth not set, so we are using the content's implicit width
|
||||
// -> we expand the dialog's width to accommodate the scrollbar width (to respect the content's desired width)
|
||||
|
||||
// don't enforce preferred width and height if not set (-1), and expand to a larger implicit size
|
||||
property real preferredWidth: Math.max(root.preferredWidth, calculatedImplicitWidth)
|
||||
property real preferredHeight: Math.max(root.preferredHeight - otherHeights, calculatedImplicitHeight)
|
||||
|
||||
property real maximumWidth: calculatedMaximumWidth
|
||||
property real maximumHeight: calculatedMaximumHeight - otherHeights // we enforce maximum height solely from the content
|
||||
|
||||
implicitWidth: Math.min(preferredWidth, maximumWidth)
|
||||
implicitHeight: Math.min(preferredHeight, maximumHeight)
|
||||
|
||||
// give an implied width and height to the contentItem so that features like word wrapping/eliding work
|
||||
// cannot placed directly in contentControl as a child, so we must use a property
|
||||
property var widthHint: Binding {
|
||||
target: contentControl.contentChildren[0] || null
|
||||
property: "width"
|
||||
value: contentControl.width + contentControl.leftPadding + contentControl.rightPadding
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
|
||||
header: T.Control {
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: verticalPadding + headerSeparator.implicitHeight // add space for bottom separator
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
id: heading
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: root.title.length === 0 ? " " : root.title // always have text to ensure header height
|
||||
elide: Text.ElideRight
|
||||
|
||||
// use tooltip for long text that is elided
|
||||
QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
|
||||
QQC2.ToolTip.text: root.title
|
||||
HoverHandler { id: titleHoverHandler }
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
visible: root.showCloseButton
|
||||
icon.name: closeIcon.hovered ? "window-close" : "window-close-symbolic"
|
||||
text: qsTr("Close", "@action:button close dialog")
|
||||
onClicked: root.reject()
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
}
|
||||
}
|
||||
|
||||
// header background
|
||||
background: Item {
|
||||
Kirigami.Separator {
|
||||
id: headerSeparator
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
visible: contentControl.contentHeight > contentControl.implicitHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use top level control rather than toolbar, since toolbar causes button rendering glitches
|
||||
footer: T.Control {
|
||||
id: footerToolBar
|
||||
|
||||
// if there is nothing in the footer, still maintain a height so that we can create a rounded bottom buffer for the dialog
|
||||
property bool bufferMode: !root.footerLeadingComponent && !dialogButtonBox.visible
|
||||
implicitHeight: bufferMode ? Math.round(Kirigami.Units.smallSpacing / 2) : contentItem.implicitHeight + topPadding + bottomPadding
|
||||
implicitWidth: dialogButtonBox.implicitWidth + leftPadding + rightPadding
|
||||
|
||||
padding: !bufferMode ? Kirigami.Units.largeSpacing : 0
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: footerToolBar.spacing
|
||||
// Don't let user interact with footer during transitions
|
||||
enabled: root.opened
|
||||
|
||||
Loader {
|
||||
id: leadingLoader
|
||||
sourceComponent: root.footerLeadingComponent
|
||||
}
|
||||
|
||||
// footer buttons
|
||||
QQC2.DialogButtonBox {
|
||||
// we don't explicitly set padding, to let the style choose the padding
|
||||
id: dialogButtonBox
|
||||
standardButtons: root.standardButtons
|
||||
visible: count > 0
|
||||
padding: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: dialogButtonBox.alignment
|
||||
|
||||
position: QQC2.DialogButtonBox.Footer
|
||||
|
||||
// ensure themes don't add a background, since it can lead to visual inconsistencies
|
||||
// with the rest of the dialog
|
||||
background: null
|
||||
|
||||
// we need to hook all of the buttonbox events to the dialog events
|
||||
onAccepted: root.accept()
|
||||
onRejected: root.reject()
|
||||
onApplied: root.applied()
|
||||
onDiscarded: root.discarded()
|
||||
onHelpRequested: root.helpRequested()
|
||||
onReset: root.reset()
|
||||
|
||||
// add custom footer buttons
|
||||
Repeater {
|
||||
id: customFooterButtons
|
||||
model: root.__visibleCustomFooterActions
|
||||
// we have to use Button instead of ToolButton, because ToolButton has no visual distinction when disabled
|
||||
delegate: QQC2.Button {
|
||||
required property T.Action modelData
|
||||
|
||||
flat: root.flatFooterButtons
|
||||
action: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: trailingLoader
|
||||
sourceComponent: root.footerTrailingComponent
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
Kirigami.Separator {
|
||||
id: footerSeparator
|
||||
visible: contentControl.contentHeight > contentControl.implicitHeight && footerToolBar.padding !== 0
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
|
||||
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
|
||||
|
||||
/**
|
||||
* A dialog that prompts users with a context menu, with
|
||||
* list items that perform actions.
|
||||
*
|
||||
* Example usage:
|
||||
* @code{.qml}
|
||||
* Kirigami.MenuDialog {
|
||||
* title: i18n("Track Options")
|
||||
*
|
||||
* actions: [
|
||||
* Kirigami.Action {
|
||||
* icon.name: "media-playback-start"
|
||||
* text: i18nc("Start playback of the selected track", "Play")
|
||||
* tooltip: i18n("Start playback of the selected track")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* enabled: false
|
||||
* icon.name: "document-open-folder"
|
||||
* text: i18nc("Show the file for this song in the file manager", "Show in folder")
|
||||
* tooltip: i18n("Show the file for this song in the file manager")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "documentinfo"
|
||||
* text: i18nc("Show track metadata", "View details")
|
||||
* tooltip: i18n("Show track metadata")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "list-add"
|
||||
* text: i18nc("Add the track to the queue, right after the current track", "Play next")
|
||||
* tooltip: i18n("Add the track to the queue, right after the current track")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "list-add"
|
||||
* text: i18nc("Enqueue current track", "Add to queue")
|
||||
* tooltip: i18n("Enqueue current track")
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see Dialog
|
||||
* @see PromptDialog
|
||||
* @inherit org::kde::kirigami::Dialog
|
||||
*/
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief This property holds the actions displayed in the context menu.
|
||||
*/
|
||||
property list<T.Action> actions
|
||||
|
||||
/**
|
||||
* @brief This property holds the content header, which appears above the actions.
|
||||
* but below the header bar.
|
||||
*/
|
||||
property alias contentHeader: columnHeader.contentItem
|
||||
|
||||
/**
|
||||
* @brief This property holds the content header.
|
||||
*
|
||||
* This makes it possible to access its internal properties to, for example, change its padding:
|
||||
* ``contentHeaderControl.topPadding``
|
||||
*
|
||||
* @property QtQuick.Controls.Control contentHeaderControl
|
||||
*/
|
||||
property alias contentHeaderControl: columnHeader
|
||||
|
||||
preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
padding: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
|
||||
spacing: 0
|
||||
|
||||
QQC2.Control {
|
||||
id: columnHeader
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
required property T.Action modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
action: modelData
|
||||
visible: !(modelData instanceof Kirigami.Action) || modelData.visible
|
||||
|
||||
icon.width: Kirigami.Units.gridUnit
|
||||
icon.height: Kirigami.Units.gridUnit
|
||||
|
||||
horizontalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
leftPadding: undefined
|
||||
rightPadding: undefined
|
||||
|
||||
QQC2.ToolTip.text: modelData instanceof Kirigami.Action ? modelData.tooltip : ""
|
||||
QQC2.ToolTip.visible: QQC2.ToolTip.text.length > 0 && (Kirigami.Settings.tabletMode ? pressed : hovered)
|
||||
QQC2.ToolTip.delay: Kirigami.Settings.tabletMode ? Qt.styleHints.mousePressAndHoldInterval : Kirigami.Units.toolTipDelay
|
||||
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standardButtons: QQC2.DialogButtonBox.NoButton
|
||||
showCloseButton: true
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* A simple dialog to quickly prompt a user with information,
|
||||
* and possibly perform an action.
|
||||
*
|
||||
* Provides content padding (instead of padding outside of the scroll
|
||||
* area). Also has a default preferredWidth, as well as the `subtitle` property.
|
||||
*
|
||||
* <b>Note:</b> If a `mainItem` is specified, it will replace
|
||||
* the subtitle label, and so the respective property will have no effect.
|
||||
*
|
||||
* @see Dialog
|
||||
* @see MenuDialog
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code{.qml}
|
||||
* Kirigami.PromptDialog {
|
||||
* title: "Reset settings?"
|
||||
* subtitle: "The stored settings for the application will be deleted, with the defaults restored."
|
||||
* standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
*
|
||||
* onAccepted: console.log("Accepted")
|
||||
* onRejected: console.log("Rejected")
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Text field prompt dialog:
|
||||
*
|
||||
* @code{.qml}
|
||||
* Kirigami.PromptDialog {
|
||||
* id: textPromptDialog
|
||||
* title: qsTr("New Folder")
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.NoButton
|
||||
* customFooterActions: [
|
||||
* Kirigami.Action {
|
||||
* text: qsTr("Create Folder")
|
||||
* icon.name: "dialog-ok"
|
||||
* onTriggered: {
|
||||
* showPassiveNotification("Created");
|
||||
* textPromptDialog.close();
|
||||
* }
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* text: qsTr("Cancel")
|
||||
* icon.name: "dialog-cancel"
|
||||
* onTriggered: {
|
||||
* textPromptDialog.close();
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* QQC2.TextField {
|
||||
* placeholderText: qsTr("Folder name…")
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @inherit Dialog
|
||||
*/
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
default property alias mainItem: mainLayout.data
|
||||
|
||||
enum DialogType {
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
Information,
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* This property holds the dialogType. It can be either:
|
||||
*
|
||||
* - `PromptDialog.Success`: For a sucess message
|
||||
* - `PromptDialog.Warning`: For a warning message
|
||||
* - `PromptDialog.Error`: For an actual error
|
||||
* - `PromptDialog.Information`: For an informational message
|
||||
* - `PromptDialog.None`: No specific dialog type.
|
||||
*
|
||||
* By default, the dialogType is `Kirigami.PromptDialog.None`
|
||||
*/
|
||||
property int dialogType: Kirigami.PromptDialog.None
|
||||
|
||||
/**
|
||||
* The text to use in the dialog's contents.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
/**
|
||||
* The padding around the content, within the scroll area.
|
||||
*
|
||||
* Default is `Kirigami.Units.largeSpacing`.
|
||||
*/
|
||||
property real contentPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
/**
|
||||
* The top padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentTopPadding: contentPadding
|
||||
|
||||
/**
|
||||
* The bottom padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentBottomPadding: footer.padding === 0 ? contentPadding : 0 // add bottom padding if there is no footer
|
||||
|
||||
/**
|
||||
* The left padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentLeftPadding: contentPadding
|
||||
|
||||
/**
|
||||
* The right padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentRightPadding: contentPadding
|
||||
|
||||
/**
|
||||
* This property holds the icon name used by the PromptDialog.
|
||||
*/
|
||||
property string iconName: switch (dialogType) {
|
||||
case Kirigami.PromptDialog.Success:
|
||||
return "data-success";
|
||||
case Kirigami.PromptDialog.Warning:
|
||||
return "data-warning";
|
||||
case Kirigami.PromptDialog.Error:
|
||||
return "data-error";
|
||||
case Kirigami.PromptDialog.Information:
|
||||
return "data-information";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
padding: 0
|
||||
|
||||
header: null
|
||||
|
||||
Kirigami.Padding {
|
||||
id: wrapper
|
||||
|
||||
topPadding: root.contentTopPadding
|
||||
leftPadding: root.contentLeftPadding
|
||||
rightPadding: root.contentRightPadding
|
||||
bottomPadding: root.contentBottomPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
source: root.iconName
|
||||
visible: root.iconName.length > 0
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.title
|
||||
visible: root.title.length > 0
|
||||
elide: QQC2.Label.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
text: root.subtitle
|
||||
wrapMode: TextEdit.Wrap
|
||||
visible: text.length > 0
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* A dialog to let's you do a global search accross your applications
|
||||
* documents, chat rooms and more.
|
||||
*
|
||||
* Example usage for a chat app where we want to quickly search for a room.
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import org.kde.kitemmodels as KItemModels
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.SearchDialog {
|
||||
* id: root
|
||||
*
|
||||
* onTextChanged: {
|
||||
* sortModel.filterText = text;
|
||||
* }
|
||||
* onAccepted: listView.currentItem.clicked()
|
||||
*
|
||||
* emptyText: i18nc("Placeholder message", "No room found.")
|
||||
*
|
||||
* model: KItemModels.KSortFilterProxyModel {
|
||||
* id: sortModel
|
||||
*
|
||||
* sourceModel: RoomModel { }
|
||||
* }
|
||||
*
|
||||
* delegate: RoomDelegate {
|
||||
* onClicked: root.close()
|
||||
* }
|
||||
*
|
||||
* Shortcut {
|
||||
* sequence: "Ctrl+K"
|
||||
* onActivated: root.open()
|
||||
* }
|
||||
* }
|
||||
* @endcode{}
|
||||
*
|
||||
* @image html searchdialog.html
|
||||
*
|
||||
* @note This component is unsuitable on mobile. Instead on mobile prefer to
|
||||
* use a seperate page for the search.
|
||||
*
|
||||
* @since Kirigami 6.3
|
||||
*/
|
||||
QQC2.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* This property holds an alias to the model of the internal ListView.
|
||||
*/
|
||||
property alias model: listView.model
|
||||
|
||||
/**
|
||||
* This property holds an alias to the delegate component of the internal ListView.
|
||||
*/
|
||||
property alias delegate: listView.delegate
|
||||
|
||||
/**
|
||||
* This property holds an alias to the currentItem component of the internal ListView.
|
||||
*/
|
||||
property alias currentItem: listView.currentItem
|
||||
|
||||
/**
|
||||
* This property holds an alias to the section component of the internal ListView.
|
||||
*/
|
||||
property alias section: listView.section
|
||||
|
||||
/**
|
||||
* This property holds an alias to the content of the search field.
|
||||
*/
|
||||
property alias text: searchField.text
|
||||
|
||||
/**
|
||||
* This property holds an alias to the left actions of the seach field.
|
||||
*/
|
||||
property alias searchFieldLeftActions: searchField.leftActions
|
||||
|
||||
/**
|
||||
* This property holds an alias to the right actions of the seach field.
|
||||
*/
|
||||
property alias searchFieldRightActions: searchField.rightActions
|
||||
|
||||
/**
|
||||
* The placeholder text shown in the (empty) search field.
|
||||
*/
|
||||
property alias searchFieldPlaceholderText: searchField.placeholderText
|
||||
|
||||
/**
|
||||
* This property holds the number of search results displayed in the internal ListView.
|
||||
*/
|
||||
property alias count: listView.count
|
||||
|
||||
/**
|
||||
* This property holds an alias to the placeholder message text displayed
|
||||
* when the internal list view is empty.
|
||||
*/
|
||||
property alias emptyText: placeholder.text
|
||||
|
||||
/**
|
||||
* This property holds an alias to the placeholder message icon displayed
|
||||
* when the internal list view is empty.
|
||||
*/
|
||||
property alias emptyIcon: placeholder.icon
|
||||
|
||||
/**
|
||||
* @brief Helpful action when the list is empty
|
||||
*
|
||||
* This property holds an alias to the helpful action of the placeholder message
|
||||
* when the internal list view is empty.
|
||||
*
|
||||
* @since 6.10
|
||||
*/
|
||||
property alias emptyHelpfulAction: placeholder.helpfulAction
|
||||
|
||||
width: Math.min(Kirigami.Units.gridUnit * 35, parent.width)
|
||||
height: Math.min(Kirigami.Units.gridUnit * 20, parent.height)
|
||||
|
||||
padding: 0
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
modal: true
|
||||
|
||||
onOpened: {
|
||||
searchField.forceActiveFocus();
|
||||
searchField.text = "";
|
||||
listView.currentIndex = 0;
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: null
|
||||
|
||||
Layout.margins: Kirigami.Units.smallSpacing
|
||||
|
||||
Keys.onDownPressed: {
|
||||
const listViewHadFocus = listView.activeFocus;
|
||||
listView.forceActiveFocus();
|
||||
if (listView.currentIndex < listView.count - 1) {
|
||||
// don't move to the next entry when we just changed focus from the search field to the list view
|
||||
if (listViewHadFocus) {
|
||||
listView.currentIndex++;
|
||||
}
|
||||
} else {
|
||||
listView.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
listView.forceActiveFocus();
|
||||
if (listView.currentIndex === 0) {
|
||||
listView.currentIndex = listView.count - 1;
|
||||
} else {
|
||||
listView.currentIndex--;
|
||||
}
|
||||
}
|
||||
Keys.onPressed: (event) => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_PageDown:
|
||||
listView.forceActiveFocus();
|
||||
listView.currentIndex = Math.min(listView.count - 1, listView.currentIndex + Math.floor(listView.height / listView.currentItem.height));
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_PageUp:
|
||||
listView.forceActiveFocus();
|
||||
listView.currentIndex = Math.max(0, listView.currentIndex - Math.floor(listView.height / listView.currentItem.height));
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
focusSequence: ""
|
||||
autoAccept: false
|
||||
|
||||
onAccepted: root.accepted()
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Keys.forwardTo: searchField
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
currentIndex: 0
|
||||
clip: true
|
||||
highlightMoveDuration: 200
|
||||
Keys.forwardTo: searchField
|
||||
keyNavigationEnabled: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: placeholder
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Kirigami.Units.gridUnit * 4
|
||||
icon.name: 'system-search-symbolic'
|
||||
visible: listView.count === 0 && text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef ENUMS_H
|
||||
#define ENUMS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
namespace ApplicationHeaderStyle
|
||||
{
|
||||
Q_NAMESPACE
|
||||
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
|
||||
enum Status {
|
||||
Auto = 0,
|
||||
Breadcrumb,
|
||||
Titles,
|
||||
ToolBar, ///@since 5.48
|
||||
None, ///@since 5.48
|
||||
};
|
||||
Q_ENUM_NS(Status)
|
||||
|
||||
enum NavigationButton {
|
||||
NoNavigationButtons = 0,
|
||||
ShowBackButton = 0x1,
|
||||
ShowForwardButton = 0x2,
|
||||
};
|
||||
Q_ENUM_NS(NavigationButton)
|
||||
Q_DECLARE_FLAGS(NavigationButtons, NavigationButton)
|
||||
}
|
||||
|
||||
namespace MessageType
|
||||
{
|
||||
Q_NAMESPACE
|
||||
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
|
||||
|
||||
enum Type {
|
||||
Information = 0,
|
||||
Positive,
|
||||
Warning,
|
||||
Error,
|
||||
};
|
||||
Q_ENUM_NS(Type)
|
||||
};
|
||||
|
||||
#endif // ENUMS_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user