From 0764e25a58c586cc2b0ed0f5164769f01849a78f Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Tue, 12 Oct 2021 17:09:38 +0200 Subject: [PATCH] feat(StatusQ.Popups): introduce `StatusMenuItemDelegate` This extracts the `MenuItem` delegate used in `StatusPopupMenu` into its own component so it can be easily reused for cases where simply supplying the popup menu with `StatusMenuItem` (which is of type `Action`) isn't enough. Ideally, the `StatusMenuItemDelegate` would be called `StatusMenuItem` but that would be a breaking change. Usage: ```qml StatusPopupMenu { delegate: StatusMenuItemDelegate { ... } } --- src/StatusQ/Popups/StatusMenuItemDelegate.qml | 194 ++++++++++++++++++ src/StatusQ/Popups/StatusPopupMenu.qml | 188 +---------------- src/StatusQ/Popups/qmldir | 1 + statusq.qrc | 1 + 4 files changed, 199 insertions(+), 185 deletions(-) create mode 100644 src/StatusQ/Popups/StatusMenuItemDelegate.qml diff --git a/src/StatusQ/Popups/StatusMenuItemDelegate.qml b/src/StatusQ/Popups/StatusMenuItemDelegate.qml new file mode 100644 index 00000000..250e4532 --- /dev/null +++ b/src/StatusQ/Popups/StatusMenuItemDelegate.qml @@ -0,0 +1,194 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups 0.1 + +MenuItem { + id: statusPopupMenuItem + implicitWidth: parent ? parent.width : 0 + implicitHeight: action.enabled ? 38 : 0 + + property int subMenuIndex + property var statusPopupMenu: null + + Component.onCompleted: { + if (!!subMenu) { + subMenuIndex = statusPopupMenu.menuItemCount + statusPopupMenu.menuItemCount += 1 + } + } + + action: StatusMenuItem { + onTriggered: { statusPopupMenu.menuItemClicked(statusPopupMenuItem.subMenuIndex); } + } + + Component { + id: indicatorComponent + Item { + implicitWidth: 24 + implicitHeight: 24 + StatusIcon { + anchors.centerIn: parent + width: { + let width = statusPopupMenuItem.action.icon.width || + statusPopupMenuItem.action.iconSettings.width + + return !!width ? width : 18 + } + rotation: statusPopupMenuItem.action.iconRotation + icon: { + if (statusPopupMenuItem.subMenu && !!statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex] && + statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].icon.toString() !== "") { + return statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].icon; + } else if (!!statusPopupMenuItem.action && statusPopupMenuItem.action.icon.name !== "") { + return statusPopupMenuItem.action.icon.name; + } else if (statusPopupMenuItem.action.iconSettings.name !== "") { + return statusPopupMenuItem.action.iconSettings.name; + } else { + return ""; + } + } + color: { + let c = statusPopupMenuItem.action.iconSettings.color || + statusPopupMenuItem.action.icon.color + + if (!Qt.colorEqual(c, "transparent")) { + return c + } + switch (statusPopupMenuItem.action.type) { + case StatusMenuItem.Type.Danger: + return Theme.palette.dangerColor1 + break; + default: + return Theme.palette.primaryColor1 + } + } + } + } + } + + Component { + id: statusLetterIdenticonCmp + Item { + implicitWidth: 24 + implicitHeight: 24 + + StatusLetterIdenticon { + anchors.centerIn: parent + width: 16 + height: 16 + color: { + let subMenuItemIcon = statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex] + return subMenuItemIcon && subMenuItemIcon.color ? subMenuItemIcon.color : statusPopupMenuItem.action.iconSettings.background.color + } + name: statusPopupMenuItem.text + letterSize: 11 + } + } + } + + Component { + id: statusRoundImageCmp + + Item { + implicitWidth: 24 + implicitHeight: 24 + StatusRoundedImage { + anchors.centerIn: parent + width: statusPopupMenuItem.action.image.width + height: statusPopupMenuItem.action.image.height + image.source: statusPopupMenuItem.subMenu ? + statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].source : + statusPopupMenuItem.action.image.source + border.width: (statusPopupMenuItem.subMenu && statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].isIdenticon) || + statusPopupMenuItem.action.image.isIdenticon ? 1 : 0 + border.color: Theme.palette.directColor7 + } + } + } + + indicator: Loader { + sourceComponent: { + let subMenuItemIcon = statusPopupMenu.subMenuItemIcons && statusPopupMenu.subMenuItemIcons[parent.subMenuIndex] + + if ((parent.subMenu && subMenuItemIcon && subMenuItemIcon.source) || + statusPopupMenuItem.action.image && !!statusPopupMenuItem.action.image.source.toString()) { + return statusRoundImageCmp + } + + return (parent.subMenu && subMenuItemIcon && subMenuItemIcon.isLetterIdenticon) || + (statusPopupMenuItem.action.iconsSettings && statusPopupMenuItem.action.iconSettings.isLetterIdenticon) ? + statusLetterIdenticonCmp : indicatorComponent + } + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 8 + active: { + if (enabled) { + let hasIconSettings = !!statusPopupMenuItem.action.icon.name || + (statusPopupMenuItem.action.iconSettings && + (!!statusPopupMenuItem.action.iconSettings.name || !!statusPopupMenuItem.action.iconSettings.isLetterIdenticon)) + + let hasImageSettings = statusPopupMenuItem.action.image && !!statusPopupMenuItem.action.image.source.toString() + + return enabled && (parent.subMenu && !!statusPopupMenu.subMenuItemIcons[parent.subMenuIndex]) || hasIconSettings || hasImageSettings + } + return false + } + } + + contentItem: StatusBaseText { + anchors.left: statusPopupMenuItem.indicator.right + anchors.right: arrowIcon.visible ? arrowIcon.left : arrowIcon.right + anchors.rightMargin: 8 + anchors.leftMargin: 4 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + text: statusPopupMenuItem.text + color: { + switch (statusPopupMenuItem.action.type) { + case StatusMenuItem.Type.Danger: + return Theme.palette.dangerColor1 + break; + default: + return Theme.palette.directColor1 + } + } + font.pixelSize: 13 + elide: Text.ElideRight + visible: statusPopupMenuItem.action.enabled + } + + arrow: StatusIcon { + id: arrowIcon + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 8 + height: 16 + visible: statusPopupMenuItem.subMenu + icon: "next" + color: Theme.palette.directColor1 + } + + background: Rectangle { + color: { + if (statusPopupMenuItem.hovered) { + return statusPopupMenuItem.action.type === StatusMenuItem.Type.Danger ? Theme.palette.dangerColor3 : Theme.palette.statusPopupMenu.hoverBackgroundColor + } + return "transparent" + } + } + + MouseArea { + id: sensor + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: statusPopupMenuItem.action.enabled + onPressed: mouse.accepted = false + } +} diff --git a/src/StatusQ/Popups/StatusPopupMenu.qml b/src/StatusQ/Popups/StatusPopupMenu.qml index 64678af0..789be8ce 100644 --- a/src/StatusQ/Popups/StatusPopupMenu.qml +++ b/src/StatusQ/Popups/StatusPopupMenu.qml @@ -9,7 +9,7 @@ import StatusQ.Popups 0.1 Menu { - id: statusPopupMenu + id: root closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape topPadding: 8 bottomPadding: 8 @@ -38,190 +38,8 @@ Menu { } } - delegate: MenuItem { - id: statusPopupMenuItem - implicitWidth: parent ? parent.width : 0 - implicitHeight: action.enabled ? 38 : 0 - - property int subMenuIndex - - Component.onCompleted: { - if (!!subMenu) { - subMenuIndex = statusPopupMenu.menuItemCount - statusPopupMenu.menuItemCount += 1 - } - } - - action: StatusMenuItem { - onTriggered: { statusPopupMenu.menuItemClicked(statusPopupMenuItem.subMenuIndex); } - } - - Component { - id: indicatorComponent - Item { - implicitWidth: 24 - implicitHeight: 24 - StatusIcon { - anchors.centerIn: parent - width: { - let width = statusPopupMenuItem.action.icon.width || - statusPopupMenuItem.action.iconSettings.width - - return !!width ? width : 18 - } - rotation: statusPopupMenuItem.action.iconRotation - icon: { - if (statusPopupMenuItem.subMenu && !!statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex] && - statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].icon.toString() !== "") { - return statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].icon; - } else if (!!statusPopupMenuItem.action && statusPopupMenuItem.action.icon.name !== "") { - return statusPopupMenuItem.action.icon.name; - } else if (statusPopupMenuItem.action.iconSettings.name !== "") { - return statusPopupMenuItem.action.iconSettings.name; - } else { - return ""; - } - } - color: { - let c = statusPopupMenuItem.action.iconSettings.color || - statusPopupMenuItem.action.icon.color - - if (!Qt.colorEqual(c, "transparent")) { - return c - } - switch (statusPopupMenuItem.action.type) { - case StatusMenuItem.Type.Danger: - return Theme.palette.dangerColor1 - break; - default: - return Theme.palette.primaryColor1 - } - } - } - } - } - - Component { - id: statusLetterIdenticonCmp - Item { - implicitWidth: 24 - implicitHeight: 24 - - StatusLetterIdenticon { - anchors.centerIn: parent - width: 16 - height: 16 - color: { - let subMenuItemIcon = statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex] - return subMenuItemIcon && subMenuItemIcon.color ? subMenuItemIcon.color : statusPopupMenuItem.action.iconSettings.background.color - } - name: statusPopupMenuItem.text - letterSize: 11 - } - } - } - - Component { - id: statusRoundImageCmp - - Item { - implicitWidth: 24 - implicitHeight: 24 - StatusRoundedImage { - anchors.centerIn: parent - width: statusPopupMenuItem.action.image.width - height: statusPopupMenuItem.action.image.height - image.source: statusPopupMenuItem.subMenu ? - statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].source : - statusPopupMenuItem.action.image.source - border.width: (statusPopupMenuItem.subMenu && statusPopupMenu.subMenuItemIcons[statusPopupMenuItem.subMenuIndex].isIdenticon) || - statusPopupMenuItem.action.image.isIdenticon ? 1 : 0 - border.color: Theme.palette.directColor7 - } - } - } - - indicator: Loader { - sourceComponent: { - let subMenuItemIcon = statusPopupMenu.subMenuItemIcons[parent.subMenuIndex] - - if ((parent.subMenu && subMenuItemIcon && subMenuItemIcon.source) || - statusPopupMenuItem.action.image && !!statusPopupMenuItem.action.image.source.toString()) { - return statusRoundImageCmp - } - - return (parent.subMenu && subMenuItemIcon && subMenuItemIcon.isLetterIdenticon) || - (statusPopupMenuItem.action.iconsSettings && statusPopupMenuItem.action.iconSettings.isLetterIdenticon) ? - statusLetterIdenticonCmp : indicatorComponent - } - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 8 - active: { - if (enabled) { - let hasIconSettings = !!statusPopupMenuItem.action.icon.name || - (statusPopupMenuItem.action.iconSettings && - (!!statusPopupMenuItem.action.iconSettings.name || !!statusPopupMenuItem.action.iconSettings.isLetterIdenticon)) - - let hasImageSettings = statusPopupMenuItem.action.image && !!statusPopupMenuItem.action.image.source.toString() - - return enabled && (parent.subMenu && !!statusPopupMenu.subMenuItemIcons[parent.subMenuIndex]) || hasIconSettings || hasImageSettings - } - return false - } - } - - contentItem: StatusBaseText { - anchors.left: statusPopupMenuItem.indicator.right - anchors.right: arrowIcon.visible ? arrowIcon.left : arrowIcon.right - anchors.rightMargin: 8 - anchors.leftMargin: 4 - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - text: statusPopupMenuItem.text - color: { - switch (statusPopupMenuItem.action.type) { - case StatusMenuItem.Type.Danger: - return Theme.palette.dangerColor1 - break; - default: - return Theme.palette.directColor1 - } - } - font.pixelSize: 13 - elide: Text.ElideRight - visible: statusPopupMenuItem.action.enabled - } - - arrow: StatusIcon { - id: arrowIcon - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 8 - height: 16 - visible: statusPopupMenuItem.subMenu - icon: "next" - color: Theme.palette.directColor1 - } - - background: Rectangle { - color: { - if (statusPopupMenuItem.hovered) { - return statusPopupMenuItem.action.type === StatusMenuItem.Type.Danger ? Theme.palette.dangerColor3 : Theme.palette.statusPopupMenu.hoverBackgroundColor - } - return "transparent" - } - } - - MouseArea { - id: sensor - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: statusPopupMenuItem.action.enabled - onPressed: mouse.accepted = false - } + delegate: StatusMenuItemDelegate { + statusPopupMenu: root } background: Item { diff --git a/src/StatusQ/Popups/qmldir b/src/StatusQ/Popups/qmldir index 861f09da..1e9ef78b 100644 --- a/src/StatusQ/Popups/qmldir +++ b/src/StatusQ/Popups/qmldir @@ -3,6 +3,7 @@ module StatusQ.Popups StatusMenuSeparator 0.1 StatusMenuSeparator.qml StatusPopupMenu 0.1 StatusPopupMenu.qml StatusMenuItem 0.1 StatusMenuItem.qml +StatusMenuItemDelegate 0.1 StatusMenuItemDelegate.qml StatusMenuHeadline 0.1 StatusMenuHeadline.qml StatusModal 0.1 StatusModal.qml StatusSearchPopup 0.1 StatusSearchPopup.qml diff --git a/statusq.qrc b/statusq.qrc index 114ae3a9..b76e6a69 100644 --- a/statusq.qrc +++ b/statusq.qrc @@ -18,6 +18,7 @@ src/StatusQ/Popups/StatusMenuSeparator.qml src/StatusQ/Popups/StatusMenuHeadline.qml src/StatusQ/Popups/StatusMenuItem.qml + src/StatusQ/Popups/StatusMenuItemDelegate.qml src/StatusQ/Popups/StatusModalDivider.qml src/StatusQ/Components/qmldir src/StatusQ/Components/StatusChatListItem.qml