583 lines
22 KiB
QML
583 lines
22 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Templates 2.15 as T
|
|
import QtQuick.Window 2.15
|
|
import DelegateUI 1.0
|
|
|
|
Item {
|
|
id: control
|
|
|
|
width: compactMode ? compactWidth : defaultMenuWidth
|
|
clip: true
|
|
|
|
signal clickMenu(deep: int, menuKey: string, menuData: var)
|
|
|
|
property bool animationEnabled: DelTheme.animationEnabled
|
|
property string contentDescription: ""
|
|
property bool showEdge: false
|
|
property bool tooltipVisible: false
|
|
property bool compactMode: false
|
|
property int compactWidth: 50
|
|
property bool popupMode: false
|
|
property int popupWidth: 200
|
|
property int popupMaxHeight: control.height
|
|
property int defaultMenuIconSize: DelTheme.DelMenu.fontSize
|
|
property int defaultMenuIconSpacing: 8
|
|
property int defaultMenuWidth: 300
|
|
property int defaultMenuHeight: 40
|
|
property int defaultMenuSpacing: 4
|
|
property var defaultSelectedKey: []
|
|
property var initModel: []
|
|
|
|
Component {
|
|
id: myContentDelegate
|
|
|
|
Item {
|
|
Text {
|
|
id: __text
|
|
anchors.left: parent.left
|
|
anchors.right: __tag.left
|
|
anchors.rightMargin: 5
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: menuButton.text
|
|
font: menuButton.font
|
|
color: menuButton.colorText
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
DelTag {
|
|
id: __tag
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: 'Success'
|
|
tagState: DelTag.State_Success
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Component {
|
|
id: __menuContentDelegate
|
|
|
|
Item {
|
|
DelIconText {
|
|
id: __icon
|
|
x: menuButton.iconStart
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: menuButton.colorText
|
|
iconSize: menuButton.iconSize
|
|
iconSource: menuButton.iconSource
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
Behavior on x {
|
|
enabled: control.animationEnabled
|
|
NumberAnimation { easing.type: Easing.OutCubic; duration: DelTheme.Primary.durationMid }
|
|
}
|
|
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
|
}
|
|
|
|
Text {
|
|
id: __text
|
|
anchors.left: __icon.right
|
|
anchors.leftMargin: menuButton.iconSpacing
|
|
anchors.right: menuButton.expandedVisible ? __expandedIcon.left : parent.right
|
|
anchors.rightMargin: menuButton.iconSpacing
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: menuButton.text
|
|
font: menuButton.font
|
|
color: menuButton.colorText
|
|
elide: Text.ElideRight
|
|
|
|
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
|
}
|
|
|
|
DelIconText {
|
|
id: __expandedIcon
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: menuButton.expandedVisible
|
|
iconSource: (control.compactMode || control.popupMode) ? DelIcon.RightOutlined : DelIcon.DownOutlined
|
|
colorIcon: menuButton.colorText
|
|
transform: Rotation {
|
|
origin {
|
|
x: __expandedIcon.width * 0.5
|
|
y: __expandedIcon.height * 0.5
|
|
}
|
|
axis {
|
|
x: 1
|
|
y: 0
|
|
z: 0
|
|
}
|
|
angle: (control.compactMode || control.popupMode) ? 0 : (menuButton.expanded ? 180 : 0)
|
|
Behavior on angle { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
|
}
|
|
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: __menuDelegate
|
|
|
|
Item {
|
|
id: __rootItem
|
|
width: ListView.view.width
|
|
height: {
|
|
switch (menuType) {
|
|
case "item":
|
|
case "group":
|
|
return __layout.height;
|
|
case "divider":
|
|
return __dividerLoader.height;
|
|
default:
|
|
return __layout.height;
|
|
}
|
|
}
|
|
clip: true
|
|
Component.onCompleted: {
|
|
if (menuType == "item" || menuType == "group") {
|
|
layerPopup = __private.createPopupList(view.menuDeep);
|
|
let list = []
|
|
for (let i = 0; i < menuChildren.length; i++) {
|
|
list.push(menuChildren[i]);
|
|
}
|
|
__childrenListView.model = list;
|
|
if (control.defaultSelectedKey.length != 0) {
|
|
if (control.defaultSelectedKey.indexOf(menuKey) != -1) {
|
|
__rootItem.expandParent();
|
|
__menuButton.clicked();
|
|
}
|
|
}
|
|
}
|
|
if (__rootItem.menuKey !== "" && __rootItem.menuKey === __private.gotoMenuKey) {
|
|
__rootItem.expandParent();
|
|
__menuButton.clicked();
|
|
}
|
|
}
|
|
|
|
required property var modelData
|
|
property alias model: __rootItem.modelData
|
|
property var view: ListView.view
|
|
property string menuKey: model.key || ""
|
|
property string menuType: model.type || "item"
|
|
property bool menuEnabled: model.enabled === undefined ? true : model.enabled
|
|
property string menuLabel: model.label || ""
|
|
property int menuHeight: model.height || defaultMenuHeight
|
|
property int menuIconSize: model.iconSize || defaultMenuIconSize
|
|
property int menuIconSource: model.iconSource || 0
|
|
property int menuIconSpacing: model.iconSpacing || defaultMenuIconSpacing
|
|
property var menuChildren: model.menuChildren || []
|
|
property int menuChildrenLength: menuChildren ? menuChildren.length : 0
|
|
property var menuContentDelegate: model.contentDelegate ?? __menuContentDelegate
|
|
|
|
property var parentMenu: view.menuDeep === 0 ? null : view.parentMenu
|
|
property bool isCurrent: __private.selectedItem === __rootItem || isCurrentParent
|
|
property bool isCurrentParent: false
|
|
property var layerPopup: null
|
|
|
|
function expandMenu() {
|
|
if (__menuButton.expandedVisible)
|
|
__menuButton.expanded = true;
|
|
}
|
|
|
|
/*! 查找当前菜单的根菜单 */
|
|
function findRootMenu() {
|
|
let parent = parentMenu;
|
|
while (parent !== null) {
|
|
if (parent.parentMenu === null)
|
|
return parent;
|
|
parent = parent.parentMenu;
|
|
}
|
|
/*! 根菜单返回自身 */
|
|
return __rootItem;
|
|
}
|
|
/*! 展开当前菜单的所有父菜单 */
|
|
function expandParent() {
|
|
let parent = parentMenu;
|
|
while (parent !== null) {
|
|
if (parent.parentMenu === null) {
|
|
parent.expandMenu();
|
|
return;
|
|
}
|
|
parent.expandMenu();
|
|
parent = parent.parentMenu;
|
|
}
|
|
}
|
|
/*! 清除当前菜单的所有子菜单 */
|
|
function clearIsCurrentParent() {
|
|
isCurrentParent = false;
|
|
for (let i = 0; i < __childrenListView.count; i++) {
|
|
let item = __childrenListView.itemAtIndex(i);
|
|
if (item)
|
|
item.clearIsCurrentParent();
|
|
}
|
|
}
|
|
/*! 选中当前菜单的所有父菜单 */
|
|
function selectedCurrentParentMenu() {
|
|
for (let i = 0; i < __listView.count; i++) {
|
|
let item = __listView.itemAtIndex(i);
|
|
if (item)
|
|
item.clearIsCurrentParent();
|
|
}
|
|
let parent = parentMenu;
|
|
while (parent !== null) {
|
|
parent.isCurrentParent = true;
|
|
if (parent.parentMenu === null)
|
|
return;
|
|
parent = parent.parentMenu;
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: __private
|
|
function onGotoMenu(key) {
|
|
if (__rootItem.menuKey !== "" && __rootItem.menuKey === key) {
|
|
__rootItem.expandParent();
|
|
__menuButton.clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: __dividerLoader
|
|
height: 5
|
|
width: parent.width
|
|
active: __rootItem.menuType == "divider"
|
|
sourceComponent: DelDivider { }
|
|
}
|
|
|
|
Item {
|
|
id: __layout
|
|
width: parent.width
|
|
height: __menuButton.height + ((control.compactMode || control.popupMode) ? 0 : __childrenListView.height)
|
|
visible: menuType == "item" || menuType == "group"
|
|
|
|
MenuButton {
|
|
id: __menuButton
|
|
width: parent.width
|
|
height: __rootItem.menuHeight
|
|
enabled: __rootItem.menuEnabled
|
|
text: (control.compactMode && __rootItem.view.menuDeep === 0) ? "" : __rootItem.menuLabel
|
|
checkable: true
|
|
iconSize: __rootItem.menuIconSize
|
|
iconSource: __rootItem.menuIconSource
|
|
iconSpacing: __rootItem.menuIconSpacing
|
|
iconStart: (control.compactMode && __rootItem.view.menuDeep === 0) ? (width - iconSize - leftPadding - rightPadding) * 0.5 : 0
|
|
expandedVisible: {
|
|
if (__rootItem.menuType == "group" ||
|
|
(control.compactMode && __rootItem.view.menuDeep === 0))
|
|
return false;
|
|
else
|
|
return __rootItem.menuChildrenLength > 0
|
|
}
|
|
isCurrent: __rootItem.isCurrent
|
|
isGroup: __rootItem.menuType == "group"
|
|
model: __rootItem.model
|
|
contentDelegate: __rootItem.menuContentDelegate
|
|
onClicked: {
|
|
if (__rootItem.menuChildrenLength == 0) {
|
|
control.clickMenu(__rootItem.view.menuDeep, __rootItem.menuKey, model);
|
|
__private.selectedItem = __rootItem;
|
|
__rootItem.selectedCurrentParentMenu();
|
|
if (control.compactMode || control.popupMode)
|
|
__rootItem.layerPopup.closeWithParent();
|
|
} else {
|
|
if (control.compactMode || control.popupMode) {
|
|
const h = __rootItem.layerPopup.topPadding +
|
|
__rootItem.layerPopup.bottomPadding +
|
|
__childrenListView.realHeight + 6;
|
|
const pos = mapToItem(null, 0, 0);
|
|
const pos2 = mapToItem(control, 0, 0);
|
|
if ((pos.y + h) > __private.window.height) {
|
|
__rootItem.layerPopup.y = Math.max(0, pos2.y - ((pos.y + h) - __private.window.height));
|
|
} else {
|
|
__rootItem.layerPopup.y = pos2.y;
|
|
}
|
|
__rootItem.layerPopup.current = __childrenListView;
|
|
__rootItem.layerPopup.open();
|
|
}
|
|
}
|
|
}
|
|
|
|
DelToolTip {
|
|
position: control.compactMode || control.popupMode ? DelToolTip.Position_Right : DelToolTip.Position_Bottom
|
|
text: __rootItem.menuLabel
|
|
visible: {
|
|
if (control.compactMode || control.popupMode)
|
|
return (__rootItem.layerPopup && !__rootItem.layerPopup.opened) ? parent.hovered : false;
|
|
else {
|
|
return control.tooltipVisible ? parent.hovered : false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: __childrenListView
|
|
visible: __rootItem.menuEnabled
|
|
parent: {
|
|
if (__rootItem.layerPopup && __rootItem.layerPopup.current === __childrenListView)
|
|
return __rootItem.layerPopup.contentItem;
|
|
else
|
|
return __layout;
|
|
}
|
|
height: {
|
|
if (__rootItem.menuType == "group" || __menuButton.expanded)
|
|
return realHeight;
|
|
else if (parent != __layout)
|
|
return parent.height;
|
|
else
|
|
return 0;
|
|
}
|
|
anchors.top: parent ? (parent == __layout ? __menuButton.bottom : parent.top) : undefined
|
|
anchors.topMargin: parent == __layout ? control.defaultMenuSpacing : 0
|
|
anchors.left: parent ? parent.left : undefined
|
|
anchors.leftMargin: (control.compactMode || control.popupMode) ? 0 : __menuButton.iconSize * menuDeep
|
|
anchors.right: parent ? parent.right : undefined
|
|
spacing: control.defaultMenuSpacing
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
interactive: __childrenListView.visible
|
|
model: []
|
|
delegate: __menuDelegate
|
|
onContentHeightChanged: cacheBuffer = contentHeight;
|
|
T.ScrollBar.vertical: DelScrollBar {
|
|
id: childrenScrollBar
|
|
visible: (control.compactMode || control.popupMode) && childrenScrollBar.size !== 1
|
|
}
|
|
clip: true
|
|
/* 子 ListView 从父 ListView 的深度累加可实现自动计算 */
|
|
property int menuDeep: __rootItem.view.menuDeep + 1
|
|
property var parentMenu: __rootItem
|
|
property int realHeight: (contentHeight + ((count === 0 || control.compactMode || control.popupMode) ? 0 : control.defaultMenuSpacing))
|
|
Behavior on height {
|
|
enabled: control.animationEnabled
|
|
NumberAnimation { duration: DelTheme.Primary.durationFast }
|
|
}
|
|
|
|
Connections {
|
|
target: control
|
|
function onCompactModeChanged() {
|
|
if (__rootItem.layerPopup) {
|
|
__rootItem.layerPopup.current = null;
|
|
__rootItem.layerPopup.close();
|
|
}
|
|
}
|
|
function onPopupModeChanged() {
|
|
if (__rootItem.layerPopup) {
|
|
__rootItem.layerPopup.current = null;
|
|
__rootItem.layerPopup.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onInitModelChanged: {
|
|
clear();
|
|
let list = [];
|
|
for (let object of initModel) {
|
|
append(object);
|
|
}
|
|
}
|
|
|
|
function gotoMenu(key) {
|
|
__private.gotoMenuKey = key;
|
|
__private.gotoMenu(key);
|
|
}
|
|
|
|
function get(index) {
|
|
if (index >= 0 && index < __listView.model.length) {
|
|
return __listView.model[index];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function set(index, object) {
|
|
if (index >= 0 && index < __listView.model.length) {
|
|
__private.model[index] = object;
|
|
__listView.model = __private.model;
|
|
}
|
|
}
|
|
|
|
function setProperty(index, propertyName, value) {
|
|
if (index >= 0 && index < __listView.model.length) {
|
|
__private.model[index][propertyName] = value;
|
|
__listView.model = __private.model;
|
|
}
|
|
}
|
|
|
|
function move(from, to, count = 1) {
|
|
if (from >= 0 && from < __listView.model.length && to >= 0 && to < __listView.model.length) {
|
|
const objects = __listView.model.splice(from, count);
|
|
__private.model.splice(to, 0, ...objects);
|
|
__listView.model = __private.model;
|
|
}
|
|
}
|
|
|
|
function insert(index, object) {
|
|
__private.model.splice(index, 0, object);
|
|
__listView.model = __private.model;
|
|
}
|
|
|
|
function append(object) {
|
|
__private.model.push(object);
|
|
__listView.model = __private.model;
|
|
}
|
|
|
|
function remove(index, count = 1) {
|
|
if (index >= 0 && index < __listView.model.length) {
|
|
__private.model.splice(index, count);
|
|
__listView.model = __private.model;
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
__private.gotoMenuKey = '';
|
|
__listView.model = [];
|
|
}
|
|
|
|
component MenuButton: DelButton {
|
|
id: __menuButtonImpl
|
|
property int iconSource: 0
|
|
property int iconSize: DelTheme.DelMenu.fontSize
|
|
property int iconSpacing: 5
|
|
property int iconStart: 0
|
|
property bool expanded: false
|
|
property bool expandedVisible: false
|
|
property bool isCurrent: false
|
|
property bool isGroup: false
|
|
property var model: undefined
|
|
property var contentDelegate: null
|
|
|
|
onClicked: {
|
|
if (expandedVisible)
|
|
expanded = !expanded;
|
|
}
|
|
hoverCursorShape: (isGroup && !control.compactMode) ? Qt.ArrowCursor : Qt.PointingHandCursor
|
|
effectEnabled: false
|
|
colorBorder: "transparent"
|
|
colorText: {
|
|
if (enabled) {
|
|
if (isGroup) {
|
|
return (isCurrent && control.compactMode) ? DelTheme.DelMenu.colorTextActive : DelTheme.DelMenu.colorTextDisabled;
|
|
} else {
|
|
return isCurrent ? DelTheme.DelMenu.colorTextActive : DelTheme.DelMenu.colorText;
|
|
}
|
|
} else {
|
|
return DelTheme.DelMenu.colorTextDisabled;
|
|
}
|
|
}
|
|
colorBg: {
|
|
if (enabled) {
|
|
if (isGroup)
|
|
return (isCurrent && control.compactMode) ? DelTheme.DelMenu.colorBgActive : DelTheme.DelMenu.colorBgDisabled;
|
|
else if (isCurrent)
|
|
return DelTheme.DelMenu.colorBgActive;
|
|
else if (hovered) {
|
|
return DelTheme.DelMenu.colorBgHover;
|
|
} else {
|
|
return DelTheme.DelMenu.colorBg;
|
|
}
|
|
} else {
|
|
return DelTheme.DelMenu.colorBgDisabled;
|
|
}
|
|
}
|
|
contentItem: Loader {
|
|
sourceComponent: __menuButtonImpl.contentDelegate
|
|
property alias model: __menuButtonImpl.model
|
|
property alias menuButton: __menuButtonImpl
|
|
}
|
|
}
|
|
|
|
Behavior on width {
|
|
enabled: control.animationEnabled
|
|
NumberAnimation {
|
|
easing.type: Easing.OutCubic
|
|
duration: DelTheme.Primary.durationMid
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: __private
|
|
signal gotoMenu(key: string)
|
|
property string gotoMenuKey: ''
|
|
property var model: []
|
|
property var window: Window.window
|
|
property var selectedItem: null
|
|
property var popupList: []
|
|
function createPopupList(deep) {
|
|
/*! 为每一层创建一个弹窗 */
|
|
if (popupList[deep] === undefined) {
|
|
let parentPopup = deep > 0 ? popupList[deep - 1] : null;
|
|
popupList[deep] = __popupComponent.createObject(control, { parentPopup: parentPopup });
|
|
}
|
|
return popupList[deep];
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
width: 1
|
|
height: parent.height
|
|
anchors.right: parent.right
|
|
active: control.showEdge
|
|
sourceComponent: Rectangle {
|
|
color: DelTheme.DelMenu.colorEdge
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onWheel: (wheel) => wheel.accepted = true;
|
|
}
|
|
|
|
Component {
|
|
id: __popupComponent
|
|
|
|
DelPopup {
|
|
width: control.popupWidth
|
|
height: current ? Math.min(control.popupMaxHeight, current.realHeight + topPadding + bottomPadding) : 0
|
|
padding: 5
|
|
onAboutToShow: {
|
|
let toX = control.width + 4;
|
|
if (parentPopup) {
|
|
toX += parentPopup.width + 4;
|
|
}
|
|
x = toX;
|
|
}
|
|
property var current: null
|
|
property var parentPopup: null
|
|
function closeWithParent() {
|
|
close();
|
|
let p = parentPopup;
|
|
while (p) {
|
|
p.close();
|
|
p = p.parentPopup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: __listView
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 8
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
anchors.margins: 5
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
spacing: control.defaultMenuSpacing
|
|
delegate: __menuDelegate
|
|
onContentHeightChanged: cacheBuffer = contentHeight;
|
|
T.ScrollBar.vertical: DelScrollBar {
|
|
anchors.rightMargin: -8
|
|
policy: control.compactMode ? DelScrollBar.AsNeeded : DelScrollBar.AlwaysOn
|
|
}
|
|
property int menuDeep: 0
|
|
}
|
|
|
|
Accessible.role: Accessible.Tree
|
|
Accessible.description: control.contentDescription
|
|
}
|