feat(fullscreen): 全屏模式添加应用分组model,前端适配分组model

This commit is contained in:
hewenfei 2024-01-15 07:16:59 +08:00
parent dacc9d4608
commit 07767c2a5b
13 changed files with 632 additions and 30 deletions

View File

@ -49,7 +49,7 @@ set(SingleApplication "qtsingleapplication")
# include
include_directories(src)
include_directories(src/model)
#include_directories(src/model)
include_directories(src/appdata)
include_directories(src/libappdata)
include_directories(src/settings)
@ -131,6 +131,7 @@ set(SOURCE_FILES
src/libappdata/app-list-plugin.cpp src/libappdata/app-list-plugin.h
src/libappdata/app-search-plugin.cpp src/libappdata/app-search-plugin.h
src/libappdata/app-category-plugin.cpp src/libappdata/app-category-plugin.h
src/libappdata/app-group-model.cpp src/libappdata/app-group-model.h
)

View File

@ -9,6 +9,8 @@ MouseArea {
id: control
hoverEnabled: true
property alias displayName: labelText.text
// ToolTip.text: comment
// ToolTip.visible: control.containsMouse
// ToolTip.delay: 500
@ -18,11 +20,12 @@ MouseArea {
radius: Platform.Theme.minRadius
useStyleTransparency: false
paletteRole: Platform.Theme.Text
alpha: control.containsPress ? 0.16 : control.containsMouse ? 0.08 : 0.00
alpha: control.containsPress ? 0.15 : control.containsMouse ? 0.08 : 0.00
RowLayout {
anchors.fill: parent
UkuiItems.StyleText {
id: labelText
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 12
@ -30,7 +33,6 @@ MouseArea {
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
font.bold: true
text: section
elide: Text.ElideRight
}

View File

@ -105,7 +105,8 @@ AppListView {
AppControls2.LabelItem {
width: ListView.view.width
height: appListView.itemHeight
focus: true;
focus: true
displayName: section
onClicked: labelItemClicked();
Keys.onPressed: {
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {

View File

@ -30,10 +30,14 @@ UkuiItems.StyleBackground {
width: count > 0 ? (childrenRect.width + maring*2) : 0
useStyleTransparency: false
paletteRole: Platform.Theme.Text
radius: height/2
alpha: 0.06
alpha: 0.03
useStyleTransparency: false
paletteRole: Platform.Theme.WindowText
border.width: 1
borderAlpha: 0.06
borderColor: Platform.Theme.WindowText
Row {
id: mainLayout
@ -54,9 +58,11 @@ UkuiItems.StyleBackground {
ToolTip.visible: modelData.toolTip !== "" && containsMouse
background.radius: width / 2
background.alpha: modelData.checked ? 0.75 : containsMouse ? 0.4 : 0
background.paletteRole: Platform.Theme.Highlight
background.alpha: modelData.checked ? 1 : containsMouse ? 0.3 : 0
icon.source: modelData.icon
icon.mode: UkuiItems.Icon.AutoHighlight
icon.mode: modelData.checked ? UkuiItems.Icon.Highlight : UkuiItems.Icon.AutoHighlight
onClicked: {
// action

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2024, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: hxf <hewenfei@kylinos.cn>
*
*/
import QtQuick 2.12
import QtQuick.Layouts 1.12
import org.ukui.quick.items 1.0 as UkuiItems
import org.ukui.quick.platform 1.0 as Platform
MouseArea {
id: root
property alias icon: appIcon
property alias text: appName
property alias background: styleBackground
hoverEnabled: true
UkuiItems.StyleBackground {
id: styleBackground
anchors.fill: parent
radius: Platform.Theme.normalRadius
useStyleTransparency: false
paletteRole: Platform.Theme.WindowText
alpha: root.containsPress ? 0.15 : root.containsMouse ? 0.08 : 0
ColumnLayout {
anchors.fill: parent
anchors.topMargin: 16
spacing: 0
UkuiItems.Icon {
id: appIcon
Layout.minimumWidth: 32
Layout.minimumHeight: 32
Layout.maximumWidth: 96
Layout.maximumHeight: 96
Layout.preferredWidth: 96
Layout.preferredHeight: 96
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
}
UkuiItems.StyleText {
id: appName
Layout.fillWidth: true
Layout.fillHeight: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
paletteRole: Platform.Theme.Text
}
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2024, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: hxf <hewenfei@kylinos.cn>
*
*/
import QtQuick 2.12
import QtQml.Models 2.12
import QtQuick.Layouts 1.12
import org.ukui.menu.core 1.0
import AppControls2 1.0 as AppControls2
import org.ukui.quick.items 1.0 as UkuiItems
import org.ukui.quick.platform 1.0 as Platform
ListView {
id: root
property int itemHeight: 40
property alias sourceModel: appGroupModel.sourceModel
spacing: 5
clip: true
boundsBehavior: Flickable.StopAtBounds
model: AppGroupModel {
id: appGroupModel
}
delegate: Item {
width: ListView.view.width
height: childrenRect.height
Column {
width: parent.width
height: childrenRect.height
spacing: 0
AppControls2.LabelItem {
width: parent.width
height: root.itemHeight
displayName: model.name
}
GridView {
width: parent.width
height: childrenRect.height
// TODO:
cellWidth: width / 8
cellHeight: cellWidth
interactive: false
model: DelegateModel {
model: appGroupModel
rootIndex: modelIndex(index)
delegate: Item {
width: GridView.view.cellWidth
height: GridView.view.cellHeight
FullScreenAppItem {
anchors.fill: parent
anchors.margins: 12
acceptedButtons: Qt.LeftButton | Qt.RightButton
text.text: model.name
icon.source: model.icon
onClicked: (event) => {
if (event.button === Qt.LeftButton) {
// openApplication
appManager.launchApp(id);
} else if (mouse.button === Qt.RightButton) {
// appListView.model.openMenu(index, MenuInfo.FullScreen);
}
}
}
}
}
}
}
}
}

View File

@ -1,8 +1,12 @@
import QtQuick 2.0
import QtQuick.Layouts 1.12
import org.ukui.quick.platform 1.0 as Platform
import org.ukui.quick.items 1.0 as UkuiItems
import QtQml.Models 2.12
import org.ukui.menu.core 1.0
import AppControls2 1.0 as AppControls2
import org.ukui.quick.items 1.0 as UkuiItems
import org.ukui.quick.platform 1.0 as Platform
UkuiItems.StyleBackground {
paletteRole: Platform.Theme.Dark
@ -23,6 +27,7 @@ UkuiItems.StyleBackground {
Item {
id: mainContainer
anchors.fill: parent
z: 10
AppPageBackend {
id: appPageBackend
@ -81,10 +86,6 @@ UkuiItems.StyleBackground {
appPageBackend.startSearch(text);
}
}
Behavior on opacity {
NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
}
}
}
@ -114,21 +115,13 @@ UkuiItems.StyleBackground {
}
// : [row: 1, column: 1]
Item {
FullScreenAppList {
Layout.row: 1
Layout.column: 1
Layout.fillWidth: true
Layout.fillHeight: true
AppList {
anchors.fill: parent
visible: true
model: appPageBackend.appModel
view.onContentYChanged: {
//
}
}
sourceModel: appPageBackend.appModel
}
}
}

View File

@ -13,4 +13,6 @@ PluginSelectButton 1.0 PluginSelectButton.qml
FullScreenHeader 1.0 FullScreenHeader.qml
FullScreenContent 1.0 FullScreenContent.qml
FullScreenFooter 1.0 FullScreenFooter.qml
FullScreenAppList 1.0 FullScreenAppList.qml
FullScreenAppItem 1.0 FullScreenAppItem.qml
Folder 1.0 Folder.qml

View File

@ -36,5 +36,7 @@
<file>AppUI/EditText.qml</file>
<file>AppUI/Folder.qml</file>
<file>AppUI/AppPageSearch.qml</file>
<file>AppUI/FullScreenAppList.qml</file>
<file>AppUI/FullScreenAppItem.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,348 @@
/*
* Copyright (C) 2024, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: hxf <hewenfei@kylinos.cn>
*
*/
#include "app-group-model.h"
#include <QDebug>
namespace UkuiMenu {
AppGroupModel::AppGroupModel(QObject *parent) : QAbstractProxyModel(parent)
{
}
QModelIndex AppGroupModel::index(int row, int column, const QModelIndex &parent) const
{
if (column != 0 || row < 0) {
return {};
}
// 通过parent找到所属的组将组的信息放入index附带数据中作为判断依据
if (parent.isValid()) {
const auto subItems = m_groups.at(parent.row());
if (row >= subItems->size()) {
return {};
}
return createIndex(row, column, subItems);
}
return createIndex(row, column, nullptr);
}
QModelIndex AppGroupModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) {
return {};
}
auto subItems = static_cast<QVector<int>*>(child.internalPointer());
if (subItems) {
return createIndex(m_groups.indexOf(subItems), 0);
}
return {};
}
bool AppGroupModel::hasChildren(const QModelIndex &parent) const
{
if (!sourceModel()) {
return false;
}
// root
if (!parent.isValid()) {
return !m_groups.isEmpty();
}
// child, 两层
if (parent.parent().isValid()) {
return false;
}
return true;
}
int AppGroupModel::rowCount(const QModelIndex &parent) const
{
if (!sourceModel()) {
return 0;
}
// root
if (!parent.isValid()) {
return m_groups.size();
}
if (parent.parent().isValid()) {
return 0;
}
int i = parent.row();
if (i < 0 || i >= m_groups.size()) {
return 0;
}
return m_groups.at(i)->size();
}
int AppGroupModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
QHash<int, QByteArray> AppGroupModel::roleNames() const
{
return QAbstractItemModel::roleNames();
}
void AppGroupModel::setSourceModel(QAbstractItemModel *sourceModel)
{
if (AppGroupModel::sourceModel() == sourceModel) {
return;
}
beginResetModel();
if (AppGroupModel::sourceModel()) {
AppGroupModel::sourceModel()->disconnect(this);
}
QAbstractProxyModel::setSourceModel(sourceModel);
if (sourceModel) {
rebuildAppGroups();
connect(sourceModel, &QAbstractItemModel::dataChanged, this, &AppGroupModel::onDataChanged);
connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &AppGroupModel::onLayoutChanged);
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &AppGroupModel::onRowsInserted);
connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &AppGroupModel::onRowsAboutToBeRemoved);
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [=] (const QModelIndex &parent, int first, int last) {
if (m_needRebuild) {
beginResetModel();
rebuildAppGroups();
endResetModel();
m_needRebuild = false;
} else if (!parent.isValid()) {
reLocationIndex(first, -(last - first + 1));
}
});
connect(sourceModel, &QAbstractItemModel::modelReset, this, [=] {
beginResetModel();
rebuildAppGroups();
endResetModel();
});
}
endResetModel();
}
QModelIndex AppGroupModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (!sourceModel() || !proxyIndex.isValid()) {
return {};
}
auto subItems = static_cast<QVector<int>*>(proxyIndex.internalPointer());
if (subItems) {
int idx = subItems->at(proxyIndex.row());
return sourceModel()->index(idx, 0);
}
return {};
}
QModelIndex AppGroupModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceModel() || !sourceIndex.isValid()) {
return {};
}
int groupIndex = findGroupIndex(sourceIndex);
if (groupIndex < 0) {
return {};
}
int tempIndex = m_groups.at(groupIndex)->indexOf(sourceIndex.row());
return index(tempIndex, 0, index(groupIndex, 0, QModelIndex()));
}
QVariant AppGroupModel::data(const QModelIndex &proxyIndex, int role) const
{
if (!checkIndex(proxyIndex, CheckIndexOption::IndexIsValid)) {
return {};
}
if (proxyIndex.parent().isValid()) {
return QAbstractProxyModel::data(proxyIndex, role);
}
if (role == DataEntity::Name) {
return sourceModel()->index(m_groups.at(proxyIndex.row())->first(), 0).data(DataEntity::Group);
}
return {};
}
void AppGroupModel::rebuildAppGroups()
{
qDeleteAll(m_groups);
m_groups.clear();
for (int i = 0; i < sourceModel()->rowCount(); ++i) {
int groupIndex = findGroupIndex(sourceModel()->index(i, 0));
QVector<int> *subItems {nullptr};
if (groupIndex < 0) {
subItems = new QVector<int>();
m_groups.append(subItems);
} else {
subItems = m_groups[groupIndex];
}
subItems->append(i);
}
}
int AppGroupModel::findGroupIndex(const QModelIndex &sourceIndex) const
{
for (int i = 0; i < m_groups.size(); ++i) {
const QVector<int> *subItems = m_groups.at(i);
if (subItems->isEmpty()) {
continue;
}
// 使用group属性进行分组
QString groupA = sourceIndex.data(DataEntity::Group).toString();
QString groupB = sourceModel()->index(subItems->at(0), 0).data(DataEntity::Group).toString();
if (groupA == groupB) {
return i;
}
}
return -1;
}
void AppGroupModel::insertApp(int groupIndex, const QModelIndex &sourceIndex)
{
if (groupIndex < 0 || groupIndex >= m_groups.size()) {
beginInsertRows(QModelIndex(), m_groups.size(), m_groups.size());
m_groups.append(new QVector<int>(1, sourceIndex.row()));
endInsertRows();
return;
}
int index = 0;
int newItem = sourceIndex.row();
QVector<int> *subItems = m_groups[groupIndex];
for (; index < subItems->size(); ++index) {
if (newItem < subItems->at(index)) {
break;
}
}
beginInsertRows(AppGroupModel::index(groupIndex, 0, QModelIndex()), index, index);
subItems->insert(index, newItem);
endInsertRows();
}
// slots
void AppGroupModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
QModelIndex proxyIndex = mapFromSource(sourceModel()->index(i, 0));
Q_EMIT dataChanged(proxyIndex, proxyIndex, roles);
}
}
void AppGroupModel::onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
{
Q_UNUSED(parents)
Q_UNUSED(hint)
beginResetModel();
rebuildAppGroups();
endResetModel();
}
void AppGroupModel::onRowsInserted(const QModelIndex &parent, int first, int last)
{
if (parent.isValid()) {
return;
}
if (first < (sourceModel()->rowCount() - 1)) {
reLocationIndex(first, (last - first + 1));
}
for (int i = first; i <= last; ++i) {
QModelIndex sourceIndex = sourceModel()->index(i, 0, parent);
insertApp(findGroupIndex(sourceIndex), sourceIndex);
}
}
void AppGroupModel::reLocationIndex(int base, int offset)
{
for (QVector<int> *group : m_groups) {
QMutableVectorIterator<int> it(*group);
while (it.hasNext()) {
const int &value = it.next();
if (value >= base) {
it.setValue(value + offset);
}
}
}
}
void AppGroupModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
{
if (parent.isValid()) {
return;
}
int groupIndex = 0, itemIndex = 0;
for (int i = first; i <= last; ++i) {
groupIndex = findGroupIndex(sourceModel()->index(i, 0));
if (groupIndex < 0) {
continue;
}
auto subItems = m_groups[groupIndex];
itemIndex = subItems->indexOf(i);
if (itemIndex < 0) {
// 如果出现错误,那么重新构建映射关系
m_needRebuild = true;
break;
}
// 删除组里的元素
beginRemoveRows(index(groupIndex, 0, QModelIndex()), itemIndex, itemIndex);
subItems->removeAt(itemIndex);
endRemoveRows();
// 删除组
if (subItems->isEmpty()) {
beginRemoveRows(QModelIndex(), groupIndex, groupIndex);
delete m_groups.takeAt(groupIndex);
endRemoveRows();
}
}
}
} // UkuiMenu

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2024, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: hxf <hewenfei@kylinos.cn>
*
*/
#ifndef UKUI_MENU_APP_GROUP_MODEL_H
#define UKUI_MENU_APP_GROUP_MODEL_H
#include <QAbstractProxyModel>
#include "data-entity.h"
namespace UkuiMenu {
/**
* @class AppGroupModel
*
* app的group属性进行分组,
*/
class AppGroupModel : public QAbstractProxyModel
{
Q_OBJECT
// Q_PROPERTY(UkuiMenu::DataEntity::PropertyName sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
public:
explicit AppGroupModel(QObject *parent = nullptr);
void setSourceModel(QAbstractItemModel *sourceModel) override;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
bool hasChildren(const QModelIndex &parent) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &proxyIndex, int role) const override;
private Q_SLOTS:
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
void onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void onRowsInserted(const QModelIndex &parent, int first, int last);
void onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
private:
void rebuildAppGroups();
void reLocationIndex(int base, int offset);
int findGroupIndex(const QModelIndex &sourceIndex) const;
void insertApp(int groupIndex, const QModelIndex &sourceIndex);
private:
// 存储分组信息
QVector<QVector<int>*> m_groups;
bool m_needRebuild {false};
};
} // UkuiMenu
#endif //UKUI_MENU_APP_GROUP_MODEL_H

View File

@ -163,7 +163,8 @@ void CombinedListModel::insertSubModel(QAbstractItemModel *subModel, int index)
m_subModels.insert(index, {subModel, subModel->rowCount()});
}
connect(subModel, &QAbstractItemModel::rowsRemoved,
// 在删除前通知上层model进行处理
connect(subModel, &QAbstractItemModel::rowsAboutToBeRemoved,
this, [subModel, this] (const QModelIndex &parent, int first, int last) {
int offset = offsetOfSubModel(subModel);
if (offset >= 0) {
@ -241,15 +242,13 @@ void CombinedListModel::removeSubModel(QAbstractItemModel *subModel)
int CombinedListModel::indexOfSubModel(QAbstractItemModel *subModel)
{
int index = -1;
for (int i = 0; i < m_subModels.size(); ++i) {
if (m_subModels.at(i).first == subModel) {
index = i;
break;
return i;
}
}
return index;
return -1;
}
} // UkuiMenu

View File

@ -31,6 +31,7 @@
#include "sidebar-button-utils.h"
#include "extension/widget-model.h"
#include "app-page-backend.h"
#include "app-group-model.h"
#include <QGuiApplication>
#include <QCommandLineParser>
@ -64,6 +65,7 @@ void UkuiMenuApplication::registerQmlTypes()
SidebarButtonUtils::defineModule(uri, versionMajor, versionMinor);
qmlRegisterType<WidgetModel>(uri, versionMajor, versionMinor, "WidgetModel");
qmlRegisterType<AppGroupModel>(uri, versionMajor, versionMinor, "AppGroupModel");
qmlRegisterType<AppPageBackend>(uri, versionMajor, versionMinor, "AppPageBackend");
qmlRegisterUncreatableType<AppListPluginGroup>(uri, versionMajor, versionMinor, "PluginGroup", "Use enums only.");