diff --git a/qml/AppUI/FullScreenContent.qml b/qml/AppUI/FullScreenContent.qml new file mode 100644 index 0000000..2e8b685 --- /dev/null +++ b/qml/AppUI/FullScreenContent.qml @@ -0,0 +1,296 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQml.Models 2.12 + +import org.ukui.menu.core 1.0 +import AppControls2 1.0 as AppControls2 + +RowLayout { + clip: true + + Item { + Layout.preferredWidth: 90 + Layout.fillHeight: true + + ListView { + id: labelListView + signal labelClicked(string labelId) + + anchors.centerIn: parent + width: parent.width + height: contentHeight > parent.height ? parent.height : contentHeight + interactive: contentHeight > parent.height + spacing: 5 + model: DelegateModel { + groups: DelegateModelGroup { + name: "disabledItem" + includeByDefault: false + } + + items.onChanged: { + for (let i = 0; i < items.count;) { + let item = items.get(i); + if (item.model.isDisable) { + items.setGroups(i, 1, "disabledItem"); + continue; + } + ++i; + } + } + + model: modelManager.getLabelModel() + delegate: MouseArea { + width: ListView.view.width + height: 30 + hoverEnabled: true + AppControls2.StyleBackground { + anchors.fill: parent + radius: 4 + paletteRole: Palette.Base + alpha: parent.containsPress ? 0.36 : parent.containsMouse ? 0.18 : 0 + useStyleTransparent: false + + AppControls2.StyleText { + anchors.fill: parent + paletteRole: Palette.Text + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: model.displayName + } + } + onClicked: { + labelListView.labelClicked(model.id) + } + } + } + } + } + + Item { + id: contentViewBase + Layout.fillWidth: true + Layout.fillHeight: true + + property int maxColumns: 8 + property int columns: { + let c = Math.floor(width / cellWidth); + return c > maxColumns ? maxColumns : c; + } + property int cellWidth: 220 + // property int cellWidth: Math.min(Math.floor(width / columns), 220) + property int cellHeight: cellWidth + property int headerHeight: 30 + + Component.onCompleted: { + changeView(modelManager.getLabelGroupModel().containLabel); + modelManager.getLabelGroupModel().containLabelChanged.connect(changeView); + } + + Component.onDestruction: { + modelManager.getLabelGroupModel().containLabelChanged.disconnect(changeView); + contentViewLoader.sourceComponent = undefined; + } + + function changeView(toLabelView) { + contentViewLoader.sourceComponent = toLabelView ? labelViewComponent : appViewComponent; + } + + Loader { + id: contentViewLoader + height: parent.height + width: contentViewBase.cellWidth * contentViewBase.columns + 2 + anchors.horizontalCenter: parent.horizontalCenter + } + + // view id: 1 + Component { + id: appViewComponent + GridView { + ScrollBar.vertical: fullScreenScrollBar + cellWidth: contentViewBase.cellWidth + cellHeight: contentViewBase.cellHeight + model: modelManager.getAppModel() + delegate: Loader { + width: GridView.view.cellWidth + height: width + property int index: model.index + property int type: model.type + property string name: model.name + property string icon: model.icon + sourceComponent: { + if (type === DataType.Normal) { + return appComponent; + } + if (type === DataType.Folder) { + return folderComponent; + } + } + } + + Component { + id: appComponent + MouseArea { + anchors.centerIn: parent + width: 100 + height: 140 + AppControls2.IconLabel { + anchors.fill: parent + iconWidth: 96 + iconHeight: 96 + appName: name + appIcon: icon + } + onClicked: { + parent.GridView.view.model.appClicked(index); + } + } + } + + Component { + id: folderComponent + Item { + MouseArea { + anchors.centerIn: parent + width: 100; height: 140 + AppControls2.IconLabel { + anchors.fill: parent + iconWidth: 96 + iconHeight: 96 + appName: name + appIcon: icon + } + } + } + } + } + } + + // view id: 2 + Component { + id: labelViewComponent + Flickable { + id: labelViewFlickable + ScrollBar.vertical: fullScreenScrollBar + contentHeight: labelColumn.height + flickableDirection: Flickable.VerticalFlick + interactive: !contentYAnimation.running + + Column { + id: labelColumn + width: parent.width + height: childrenRect.height + Repeater { + id: labelRepeater + model: modelManager.getLabelGroupModel() + delegate: GridView { + id: labelAppsGridView + width: parent.width + height: contentHeight + + property int labelIndex: index + + interactive: false + cacheBuffer: 50 + cellWidth: contentViewBase.cellWidth + cellHeight: contentViewBase.cellHeight + + header: Row { + width: labelAppsGridView.width + height: contentViewBase.headerHeight + spacing: 15 + Text { + id: labelName + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + width: contentWidth + text: name + } + + AppControls2.StyleBackground { + anchors.verticalCenter: parent.verticalCenter + paletteRole: Palette.Light + height: 1 + width: parent.width - labelName.width - parent.spacing + } + } + + model: extraData + delegate: Item { + width: GridView.view.cellWidth + height: GridView.view.cellHeight + + MouseArea { + anchors.centerIn: parent + width: 100; height: 140 + AppControls2.IconLabel { + anchors.fill: parent + iconWidth: 96 + iconHeight: 96 + appName: modelData.name + appIcon: modelData.icon + } + onClicked: { + //console.log("clicked", Object.keys(model)) + labelRepeater.model.openApp(labelAppsGridView.labelIndex, model.index); + } + } + } + } + } + } + + NumberAnimation { + id: contentYAnimation + target: labelViewFlickable + property: "contentY" + duration: 300 + onFinished: { + labelViewFlickable.returnToBounds(); + } + } + + function scrollerView(labelId) { + let labelindex = labelRepeater.model.getLabelIndex(labelId); + if (labelindex < 0 || labelindex >= labelRepeater.count) { + return; + } + + let nextY = labelRepeater.itemAt(labelindex).y; + let sh = labelColumn.height - nextY; + if (sh < height) { + nextY -= (height - sh); + } + + contentYAnimation.running = false; + if (nextY === contentY) { + return; + } + + contentYAnimation.from = contentY; + contentYAnimation.to = nextY; + contentYAnimation.running = true; + } + + Component.onCompleted: { + labelListView.labelClicked.connect(scrollerView); + } + Component.onDestruction: { + labelListView.labelClicked.disconnect(scrollerView); + } + } + } + } + + Item { + Layout.preferredWidth: 90 + Layout.fillHeight: true + + AppControls2.ScrollBar { + id: fullScreenScrollBar + anchors.horizontalCenter: parent.horizontalCenter + height: parent.height + width: 20 + } + } +} diff --git a/qml/AppUI/FullScreenFooter.qml b/qml/AppUI/FullScreenFooter.qml new file mode 100644 index 0000000..e7aa824 --- /dev/null +++ b/qml/AppUI/FullScreenFooter.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 + +Item { + Text { + anchors.fill: parent + text: qsTr("FullScreenFooter.qml") + } +} diff --git a/qml/AppUI/FullScreenHeader.qml b/qml/AppUI/FullScreenHeader.qml new file mode 100644 index 0000000..51df2f2 --- /dev/null +++ b/qml/AppUI/FullScreenHeader.qml @@ -0,0 +1,32 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import AppControls2 1.0 as AppControls2 +import org.ukui.menu.core 1.0 + +Item { + clip: true + + PluginSelectMenu { + anchors.right: parent.right + anchors.rightMargin: 72 // 48+24 + anchors.verticalCenter: parent.verticalCenter + width: 80 + height: 48 + spacing: 8 + } + + AppControls2.RoundButton { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: 48 + height: 48 + buttonIcon: "image://appicon/view-restore-symbolic" + onClicked: { + mainWindow.isFullScreen = false; + } + } +} + + diff --git a/qml/AppUI/FullScreenUI.qml b/qml/AppUI/FullScreenUI.qml index f59978c..3bde267 100644 --- a/qml/AppUI/FullScreenUI.qml +++ b/qml/AppUI/FullScreenUI.qml @@ -1,15 +1,28 @@ import QtQuick 2.0 - +import QtQuick.Layouts 1.12 Item { - Rectangle { + ColumnLayout { anchors.fill: parent - color: "yellow" + anchors.leftMargin: 35 + anchors.rightMargin: 35 - MouseArea { - anchors.fill: parent; - onClicked: { - mainWindow.isFullScreen = !mainWindow.isFullScreen; - } + FullScreenHeader { + // full screen header + Layout.preferredHeight: 130 + Layout.fillWidth: true + Layout.maximumHeight: 130 + } + + FullScreenContent { + // full screen content + Layout.fillHeight: true + Layout.fillWidth: true + } + + FullScreenFooter { + // full screen footer + Layout.preferredHeight: 90 + Layout.fillWidth: true } } } diff --git a/qml/AppUI/Sidebar.qml b/qml/AppUI/Sidebar.qml index aadf7a0..663ec34 100644 --- a/qml/AppUI/Sidebar.qml +++ b/qml/AppUI/Sidebar.qml @@ -40,6 +40,7 @@ Item { id: extensionListView width: parent.width - 34 height: parent.height + clip: true orientation: ListView.Horizontal model: extensionManager.extensionModel() delegate: headerDelegate @@ -60,6 +61,19 @@ Item { Item { width: 34 height: 34 + Rectangle { + anchors.fill: parent + radius: height / 2 + color: "lightblue" + MouseArea { + anchors.fill: parent + onClicked: { + // 全屏 + console.log("==开始全屏==") + mainWindow.isFullScreen = true; + } + } + } } } diff --git a/qml/AppUI/qmldir b/qml/AppUI/qmldir index 1263720..d09f024 100644 --- a/qml/AppUI/qmldir +++ b/qml/AppUI/qmldir @@ -7,3 +7,6 @@ FullScreenUI 1.0 FullScreenUI.qml AppPageHeader 1.0 AppPageHeader.qml SearchInputBar 1.0 SearchInputBar.qml PluginSelectMenu 1.0 PluginSelectMenu.qml +FullScreenHeader 1.0 FullScreenHeader.qml +FullScreenContent 1.0 FullScreenContent.qml +FullScreenFooter 1.0 FullScreenFooter.qml diff --git a/qml/main.qml b/qml/main.qml index f65dcd7..500b917 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify @@ -17,11 +17,98 @@ */ import QtQuick 2.12 +import QtQml 2.12 import AppUI 1.0 as AppUI +import AppControls2 1.0 as AppControls2 +import org.ukui.menu.core 1.0 Item { - AppUI.NormalUI { - anchors.fill: parent + id: root + clip: true + property int animationDuration: 150 + + Component.onCompleted: { + loader.reloadUI(); + mainWindow.beforeFullScreenChanged.connect(beforeFullScreenSlot); + mainWindow.fullScreenChanged.connect(fullScreenChangedSlot); + } + + function fullScreenChangedSlot() { + loader.opacity = 0; + fullScreenAnimation.start(); + } + + function beforeFullScreenSlot(status) { + if (!status) { + loader.sourceComponent = undefined; + } + backgroundMask.alpha = status ? 0.5 : 0 + backgroundMask.enableSizeChange = false; + } + + ParallelAnimation { + id: fullScreenAnimation + NumberAnimation { target: backgroundMask; property: "width"; to: root.width; duration: root.animationDuration } + NumberAnimation { target: backgroundMask; property: "height"; to: root.height; duration: root.animationDuration } + + onFinished: { + backgroundMask.enableSizeChange = true; + loader.reloadUI(); + } + } + + AppControls2.StyleBackground { + id: backgroundMask + anchors.left: parent.left + anchors.bottom: parent.bottom + alpha: 0 + useStyleTransparent: false + paletteRole: Palette.Dark + + property bool enableSizeChange: true + + states: [ + State { + name: "disableSizeChange" + when: !backgroundMask.enableSizeChange + AnchorChanges { + target: backgroundMask + anchors.top: undefined + anchors.right: undefined + } + }, + State { + name: "enableSizeChange" + when: backgroundMask.enableSizeChange + AnchorChanges { + target: backgroundMask + anchors.top: parent.top + anchors.right: parent.right + } + } + ] + + Loader { + id: loader + anchors.fill: parent + Behavior on opacity { + NumberAnimation { duration: root.animationDuration } + } + function reloadUI() { + loader.sourceComponent = mainWindow.isFullScreen ? fullSceenComponent : normalComponent; + loader.opacity = 1; + } + } + } + + Component { + id: fullSceenComponent + AppUI.FullScreenUI {} + } + + Component { + id: normalComponent + AppUI.NormalUI {} } } diff --git a/qml/qml.qrc b/qml/qml.qrc index d43b85e..91772c6 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -10,6 +10,9 @@ AppUI/AppPage.qml AppUI/Sidebar.qml AppUI/AppList.qml + AppUI/FullScreenHeader.qml + AppUI/FullScreenContent.qml + AppUI/FullScreenFooter.qml AppUI/AppPageHeader.qml AppUI/SearchInputBar.qml AppControls2/qmldir diff --git a/src/windows/menu-main-window.cpp b/src/windows/menu-main-window.cpp index 19e758c..43f796e 100644 --- a/src/windows/menu-main-window.cpp +++ b/src/windows/menu-main-window.cpp @@ -395,6 +395,8 @@ void MenuWindow::init() QEvent event(QEvent::Move); QCoreApplication::sendEvent(this, &event); }); + + updateGeometry(); } void MenuWindow::updateGeometry() @@ -412,13 +414,24 @@ bool MenuWindow::isFullScreen() const return m_isFullScreen; } +/** + * beforeFullScreenChanged -> (qml)onWidthChanged -> fullScreenChanged + * @param isFullScreen + */ void MenuWindow::setFullScreen(bool isFullScreen) { if (m_isFullScreen == isFullScreen) { return; } + Q_EMIT beforeFullScreenChanged(isFullScreen); + m_isFullScreen = isFullScreen; + WindowHelper::windowBlur(this, !m_isFullScreen); + + updateGeometry(); + + // 更新contentItem尺寸 QEvent event(QEvent::Resize); QCoreApplication::sendEvent(this, &event); @@ -427,24 +440,17 @@ void MenuWindow::setFullScreen(bool isFullScreen) void MenuWindow::exposeEvent(QExposeEvent *event) { + if (isExposed()) { + requestActivate(); + } QQuickWindow::exposeEvent(event); } -void MenuWindow::resizeEvent(QResizeEvent *event) -{ - updateGeometry(); - QQuickView::resizeEvent(event); -} - -void MenuWindow::showEvent(QShowEvent *event) -{ - QQuickWindow::showEvent(event); -} - void MenuWindow::focusOutEvent(QFocusEvent *event) { - //setVisible(false); + // void QQuickWindow::focusOutEvent(QFocusEvent *ev) { Q_D(QQuickWindow); if (d->contentItem) d->contentItem->setFocus(false, ev->reason()); } QQuickWindow::focusOutEvent(event); + // setVisible(false); } bool MenuWindow::event(QEvent *event) diff --git a/src/windows/menu-main-window.h b/src/windows/menu-main-window.h index 37c649f..ce51749 100644 --- a/src/windows/menu-main-window.h +++ b/src/windows/menu-main-window.h @@ -115,14 +115,13 @@ public: Q_SIGNALS: void fullScreenChanged(); + void beforeFullScreenChanged(bool isToFullScreen); private Q_SLOTS: void onActiveFocusItemChanged(); protected: void exposeEvent(QExposeEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - void showEvent(QShowEvent *event) override; void focusOutEvent(QFocusEvent *event) override; bool event(QEvent *event) override;