fix(labels): 标签界面基于新后端实现

This commit is contained in:
hewenfei 2024-01-22 16:03:53 +08:00
parent fba597ce54
commit e29052607f
19 changed files with 456 additions and 102 deletions

111
qml/AppUI/AppLabelPage.qml Normal file
View File

@ -0,0 +1,111 @@
/*
* 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/>.
*
*/
import QtQuick 2.12
import org.ukui.menu.core 1.0
import org.ukui.quick.items 1.0 as UkuiItems
import org.ukui.quick.platform 1.0 as Platform
GridView {
property var labelBottle: null
property int labelColum: labelBottle === null ? 0 : labelBottle.column
property int labelRow: Math.ceil(count / labelColum)
signal labelSelected(string label)
//
width: 200
height: childrenRect.height
cellWidth: width / labelColum
cellHeight: 40
model: labelBottle === null ? [] : labelBottle.labels
delegate: MouseArea {
width: GridView.view.cellWidth
height: GridView.view.cellHeight
focus: true
hoverEnabled: true
onClicked: {
GridView.view.labelSelected(modelData.label);
GridView.view.currentIndex = model.index
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
GridView.view.labelSelected(modelData.label);
}
}
UkuiItems.StyleBackground {
anchors.fill: parent
radius: Platform.Theme.normalRadius
useStyleTransparency: false
paletteRole: Platform.Theme.WindowText
alpha: parent.containsPress ? 0.15 : parent.containsMouse ? 0.08 : 0.0
border.width: (parent.GridView.isCurrentItem && !mainWindow.isFullScreen) ? 2 : 0
borderColor: Platform.Theme.Highlight
Text {
anchors.fill: parent
visible: modelData.type === LabelItem.Text
text: modelData.display
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
UkuiItems.Icon {
anchors.centerIn: parent
width: parent.height/2
height: parent.height/2
visible: modelData.type === LabelItem.Icon
source: modelData.type === LabelItem.Icon ? modelData.display : ""
}
}
}
Keys.onRightPressed: {
if(currentIndex === count - 1) {
currentIndex = 0;
} else {
currentIndex++;
}
}
Keys.onLeftPressed: {
if(currentIndex === 0) {
currentIndex = count - 1;
} else {
currentIndex--;
}
}
Keys.onDownPressed: {
if(Math.floor(currentIndex / labelColum) < labelRow - 1) {
currentIndex = currentIndex + labelColum;
}
}
Keys.onUpPressed: {
if(Math.floor(currentIndex / labelColum) > 0) {
currentIndex = currentIndex - labelColum;
}
}
}

View File

@ -70,7 +70,7 @@ UkuiItems.StyleBackground {
}
Keys.onPressed: {
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
pluginSortButtonRoot.model.changeProvider(0);
modelData.trigger();
}
}
}

View File

@ -52,7 +52,7 @@ MouseArea {
ListView {
id: listView
cacheBuffer: itemHeight * listView.count
cacheBuffer: count * root.itemHeight
spacing: 4
Layout.fillHeight: true
Layout.fillWidth: true

View File

@ -29,7 +29,8 @@ Item {
if (appList.visible) {
appList.resetListFocus();
} else {
selectionPage.viewFocusEnable();
selectionPage.focus = true;
labelPage.focus = true;
}
}
@ -51,7 +52,7 @@ Item {
AppList {
id: appList
anchors.fill: parent
visible: true
visible: !selectionPage.visible
model: AppPageBackend.appModel
view.onContentYChanged: {
@ -61,15 +62,95 @@ Item {
appListHeader.title = view.currentSection;
}
}
function positionLabel(label) {
let index = model.findLabelIndex(label);
if (index >= 0) {
view.positionViewAtIndex(index, ListView.Beginning)
}
}
onLabelItemClicked: {
labelPage.labelBottle = AppPageBackend.appModel.labelBottle;
labelPage.currentIndex = 0;
selectionPage.state = "viewShowed";
}
}
SelectionPage {
MouseArea {
id: selectionPage
anchors.fill: parent
anchors.bottomMargin: 54
visible: !appList.visible
onViewHideFinished: appList.visible = true
onLabelSelected: appList.labelSelection(labelId)
visible: false
onClicked: state = "viewHid";
state: "viewHid"
states: [
State {
name: "viewShowed"
changes: [
PropertyChanges {target: selectionPage; scale: 1; opacity: 1; focus: true }
]
},
State {
name: "viewHid"
changes: [
PropertyChanges {target: selectionPage; scale: 1.5; opacity: 0; focus: false }
]
}
]
transitions: [
Transition {
from: "*"; to: "viewShowed"
SequentialAnimation {
ScriptAction { script: selectionPage.visible = true; }
NumberAnimation { properties: "scale,opacity"; easing.type: Easing.InOutCubic; duration: 300}
}
},
Transition {
from: "*"; to: "viewHid"
SequentialAnimation {
NumberAnimation { properties: "scale,opacity"; easing.type: Easing.InOutCubic; duration: 300}
ScriptAction {
script: {
selectionPage.visible = false;
labelPage.labelBottle = null;
labelPage.focus = false;
appList.focus = false;
}
}
}
}
]
AppLabelPage {
id: labelPage
anchors.centerIn: parent
interactive: height > parent.height
onLabelSelected: (label) => {
appList.positionLabel(label);
selectionPage.state = "viewHid";
}
onModelChanged: {
currentIndex = -1;
}
}
function hidePage() {
state = "viewHid";
visible = false;
}
Component.onCompleted: {
mainWindow.visibleChanged.connect(hidePage)
}
Component.onDestruction: {
mainWindow.visibleChanged.disconnect(hidePage)
}
}
}
}

View File

@ -107,17 +107,60 @@ UkuiItems.StyleBackground {
anchors.centerIn: parent
width: 120
height: parent.height
AppLabelPage {
id: appLabelPage
anchors.centerIn: parent
width: parent.width
labelBottle: AppPageBackend.appModel.labelBottle
labelColum: 1
cellHeight: 34
interactive: height > parent.height
highlightMoveDuration: 300
highlight: UkuiItems.StyleBackground {
width: appLabelPage.cellWidth
height: appLabelPage.cellHeight
useStyleTransparency: false
radius: Platform.Theme.minRadius
paletteRole: Platform.Theme.Light
alpha: 0.15; borderAlpha: 0.5
border.width: 1
borderColor: Platform.Theme.Highlight
}
onLabelSelected: (label) => {
fullScreenAppList.positionLabel(label);
}
}
}
}
// : [row: 1, column: 1]
FullScreenAppList {
id: fullScreenAppList
Layout.row: 1
Layout.column: 1
Layout.fillWidth: true
Layout.fillHeight: true
sourceModel: AppPageBackend.appModel
function positionLabel(label) {
let index = model.findLabelIndex(label);
if (index >= 0) {
positionViewAtIndex(index, ListView.Beginning)
}
}
onContentYChanged: {
// 200
let index = indexAt(10, contentY + 200);
if (index >= 0) {
appLabelPage.currentIndex = index + 1;
}
}
}
}
}

View File

@ -15,4 +15,5 @@ FullScreenContent 1.0 FullScreenContent.qml
FullScreenFooter 1.0 FullScreenFooter.qml
FullScreenAppList 1.0 FullScreenAppList.qml
FullScreenAppItem 1.0 FullScreenAppItem.qml
AppLabelPage 1.0 AppLabelPage.qml
Folder 1.0 Folder.qml

View File

@ -36,5 +36,6 @@
<file>AppUI/AppPageSearch.qml</file>
<file>AppUI/FullScreenAppList.qml</file>
<file>AppUI/FullScreenAppItem.qml</file>
<file>AppUI/AppLabelPage.qml</file>
</qresource>
</RCC>

View File

@ -17,49 +17,3 @@
*/
#include "commons.h"
#include <utility>
// ====== LabelItem ====== //
UkuiMenu::LabelItem::LabelItem(bool disable, int index, QString id, QString displayName)
: m_disable(disable), m_index(index), m_id(std::move(id)), m_displayName(std::move(displayName)) {}
bool UkuiMenu::LabelItem::isDisable() const
{
return m_disable;
}
void UkuiMenu::LabelItem::setDisable(bool disable)
{
LabelItem::m_disable = disable;
}
int UkuiMenu::LabelItem::index() const
{
return m_index;
}
void UkuiMenu::LabelItem::setIndex(int index)
{
LabelItem::m_index = index;
}
const QString &UkuiMenu::LabelItem::displayName() const
{
return m_id;
}
void UkuiMenu::LabelItem::setDisplayName(const QString &name)
{
LabelItem::m_id = name;
}
const QString &UkuiMenu::LabelItem::id() const
{
return m_id;
}
void UkuiMenu::LabelItem::setId(const QString &id)
{
m_id = id;
}

View File

@ -23,43 +23,6 @@
namespace UkuiMenu {
// 标签项
class LabelItem
{
Q_GADGET
public:
enum PropertyName {
IsDisable = 0,
Index,
Id,
DisplayName
};
LabelItem() = default;
explicit LabelItem(bool disable, int index, QString id, QString displayName);
friend inline bool operator==(const LabelItem& a, const LabelItem& b) {
return QString::compare(a.id(), b.id(), Qt::CaseInsensitive);
}
bool isDisable() const;
void setDisable(bool disable);
int index() const;
void setIndex(int index);
const QString &id() const;
void setId(const QString &id);
const QString &displayName() const;
void setDisplayName(const QString &name);
private:
bool m_disable{true};
int m_index{0};
QString m_id;
QString m_displayName;
};
class Display {
Q_GADGET
public:

View File

@ -22,6 +22,7 @@
#include "combined-list-model.h"
#include "app-category-model.h"
#include "recently-installed-model.h"
#include "data-entity.h"
#include "event-track.h"
#include <QAction>
@ -30,13 +31,13 @@
namespace UkuiMenu {
AppCategoryPlugin::AppCategoryPlugin(QObject *parent) : AppListPluginInterface(parent)
, m_dataModel(new CombinedListModel(this))
, m_dataModel(new CombinedListModel(this)), m_labelBottle(new LabelBottle(this))
{
auto categoryModel = new AppCategoryModel(this);
auto recentlyModel = new RecentlyInstalledModel(this);
m_categoryModel = new AppCategoryModel(this);
m_recentlyModel = new RecentlyInstalledModel(this);
m_dataModel->insertSubModel(recentlyModel);
m_dataModel->insertSubModel(categoryModel);
m_dataModel->insertSubModel(m_recentlyModel);
m_dataModel->insertSubModel(m_categoryModel);
auto categoryAction = new QAction(QIcon::fromTheme("applications-utilities-symbolic"), tr("Category"), this);
auto firstLatterAction = new QAction(QIcon::fromTheme("ukui-capslock-symbolic"), tr("Letter Sort"), this);
@ -45,20 +46,22 @@ AppCategoryPlugin::AppCategoryPlugin(QObject *parent) : AppListPluginInterface(p
firstLatterAction->setCheckable(true);
connect(categoryAction, &QAction::triggered, this, [=] {
categoryModel->setMode(AppCategoryModel::Category);
m_categoryModel->setMode(AppCategoryModel::Category);
categoryAction->setChecked(true);
firstLatterAction->setChecked(false);
setTitle(categoryAction->text());
updateLabelBottle();
QMap<QString, QVariant> map;
map.insert(QStringLiteral("viewName"), QStringLiteral("category"));
EventTrack::instance()->sendClickEvent("switch_app_view", "AppView", map);
});
connect(firstLatterAction, &QAction::triggered, this, [=] {
categoryModel->setMode(AppCategoryModel::FirstLatter);
m_categoryModel->setMode(AppCategoryModel::FirstLatter);
categoryAction->setChecked(false);
firstLatterAction->setChecked(true);
setTitle(firstLatterAction->text());
updateLabelBottle();
QMap<QString, QVariant> map;
map.insert(QStringLiteral("viewName"), QStringLiteral("letter"));
@ -70,6 +73,7 @@ AppCategoryPlugin::AppCategoryPlugin(QObject *parent) : AppListPluginInterface(p
categoryAction->setChecked(true);
setTitle(categoryAction->text());
m_labelBottle->setColumn(2);
}
QString AppCategoryPlugin::name()
@ -107,4 +111,33 @@ void AppCategoryPlugin::setTitle(const QString &title)
Q_EMIT titleChanged();
}
LabelBottle *AppCategoryPlugin::labelBottle()
{
updateLabelBottle();
return m_labelBottle;
}
void AppCategoryPlugin::updateLabelBottle()
{
QList<LabelItem> labels;
if (m_recentlyModel->rowCount() > 0) {
labels << LabelItem(tr("Recently Installed"), "search-symbolic", LabelItem::Icon);
}
QHash<QString, int> groups;
int rowCount = m_categoryModel->rowCount();
for (int row = 0; row < rowCount; ++row) {
QString group = m_categoryModel->index(row, 0, QModelIndex()).data(DataEntity::Group).toString();
if (groups.contains(group)) {
continue;
}
groups.insert(group, 0);
labels.append(LabelItem(group, group));
}
m_labelBottle->setLabels(labels);
m_labelBottle->setColumn(m_categoryModel->mode() == AppCategoryModel::FirstLatter ? 5 : 2);
}
} // UkuiMenu

View File

@ -26,6 +26,8 @@
namespace UkuiMenu {
class CombinedListModel;
class AppCategoryModel;
class RecentlyInstalledModel;
class AppCategoryPlugin : public AppListPluginInterface
{
@ -38,13 +40,20 @@ public:
QString title() override;
QList<QAction *> actions() override;
QAbstractItemModel *dataModel() override;
LabelBottle *labelBottle() override;
private:
void setTitle(const QString &title);
void updateLabelBottle();
private:
QString m_title;
QList<QAction *> m_actions;
LabelBottle *m_labelBottle {nullptr};
AppCategoryModel *m_categoryModel {nullptr};
RecentlyInstalledModel *m_recentlyModel {nullptr};
CombinedListModel *m_dataModel {nullptr};
};

View File

@ -194,7 +194,7 @@ QVariant AppGroupModel::data(const QModelIndex &proxyIndex, int role) const
return QAbstractProxyModel::data(proxyIndex, role);
}
if (role == DataEntity::Name) {
if (role == DataEntity::Name || role == DataEntity::Group) {
return sourceModel()->index(m_groups.at(proxyIndex.row())->first(), 0).data(DataEntity::Group);
}
@ -345,4 +345,16 @@ void AppGroupModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first,
}
}
int AppGroupModel::findLabelIndex(const QString &label) const
{
int rowCount = AppGroupModel::rowCount(QModelIndex());
for (int i = 0; i < rowCount; ++i) {
if (index(i, 0, QModelIndex()).data(DataEntity::Group).toString() == label) {
return i;
}
}
return -1;
}
} // UkuiMenu

View File

@ -54,6 +54,8 @@ public:
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &proxyIndex, int role) const override;
Q_INVOKABLE int findLabelIndex(const QString &label) const;
private Q_SLOTS:
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
void onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);

View File

@ -128,6 +128,8 @@ void AppListModel::installPlugin(AppListPluginInterface *plugin)
connect(m_plugin, &AppListPluginInterface::titleChanged, this, [this, plugin] {
m_header->setTitle(plugin->title());
});
Q_EMIT labelBottleChanged();
}
void AppListModel::unInstallPlugin()
@ -141,6 +143,8 @@ void AppListModel::unInstallPlugin()
QSortFilterProxyModel::setSourceModel(nullptr);
disconnect(m_plugin, nullptr, this, nullptr);
m_plugin = nullptr;
Q_EMIT labelBottleChanged();
}
void AppListModel::openMenu(const int &index, MenuInfo::Location location) const
@ -151,4 +155,26 @@ void AppListModel::openMenu(const int &index, MenuInfo::Location location) const
}
}
LabelBottle *AppListModel::labelBottle() const
{
if (m_plugin) {
return m_plugin->labelBottle();
}
return nullptr;
}
int AppListModel::findLabelIndex(const QString &label) const
{
// TODO: 潜在的优化空间
int count = AppListModel::rowCount();
for (int i = 0; i < count; ++i) {
if (AppListModel::sourceModel()->index(i, 0).data(DataEntity::Group).toString() == label) {
return i;
}
}
return -1;
}
} // UkuiMenu

View File

@ -71,6 +71,7 @@ class AppListModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(UkuiMenu::AppListHeader *header READ getHeader NOTIFY headerChanged)
Q_PROPERTY(UkuiMenu::LabelBottle *labelBottle READ labelBottle NOTIFY labelBottleChanged)
public:
explicit AppListModel(QObject *parent = nullptr);
@ -81,15 +82,18 @@ public:
QHash<int, QByteArray> roleNames() const override;
AppListHeader *getHeader() const;
LabelBottle *labelBottle() const;
void installPlugin(AppListPluginInterface *plugin);
// reset
void unInstallPlugin();
Q_INVOKABLE void openMenu(const int &index, MenuInfo::Location location) const;
Q_INVOKABLE int findLabelIndex(const QString &label) const;
Q_SIGNALS:
void headerChanged();
void labelBottleChanged();
private:
AppListHeader *m_header {nullptr};

View File

@ -20,13 +20,15 @@
#include "app-list-plugin.h"
#include <QDebug>
#include <utility>
namespace UkuiMenu {
// ====== AppListPluginInterface ====== //
AppListPluginInterface::AppListPluginInterface(QObject *parent) : QObject(parent)
{
qRegisterMetaType<UkuiMenu::LabelItem>("LabelItem");
qRegisterMetaType<UkuiMenu::LabelBottle*>("LabelBottle*");
}
void AppListPluginInterface::search(const QString &keyword)
@ -34,4 +36,64 @@ void AppListPluginInterface::search(const QString &keyword)
Q_UNUSED(keyword)
}
LabelBottle *AppListPluginInterface::labelBottle()
{
return nullptr;
}
// ====== LabelItem ====== //
LabelItem::LabelItem(QString labelName, QString displayName, LabelItem::Type type)
: m_type(type), m_labelName(std::move(labelName)), m_displayName(std::move(displayName))
{
}
QString LabelItem::labelName() const
{
return m_labelName;
}
LabelItem::Type LabelItem::type() const
{
return m_type;
}
QString LabelItem::displayName() const
{
return m_displayName;
}
// ====== LabelBottle ====== //
LabelBottle::LabelBottle(QObject *parent) : QObject(parent)
{
}
int LabelBottle::column() const
{
return m_column;
}
QList<UkuiMenu::LabelItem> LabelBottle::labels() const
{
return m_labels;
}
void LabelBottle::setColumn(int column)
{
if (m_column == column) {
return;
}
m_column = column;
Q_EMIT columnChanged();
}
void LabelBottle::setLabels(const QList<UkuiMenu::LabelItem> &labels)
{
m_labels.clear();
m_labels.append(labels);
Q_EMIT labelsChanged();
}
} // UkuiMenu

View File

@ -29,6 +29,54 @@ class QAbstractItemModel;
namespace UkuiMenu {
class LabelItem
{
Q_GADGET
Q_PROPERTY(UkuiMenu::LabelItem::Type type READ type)
Q_PROPERTY(QString label READ labelName)
Q_PROPERTY(QString display READ displayName)
public:
enum Type {
Text,
Icon
};
Q_ENUM(Type)
explicit LabelItem(QString labelName = "", QString displayName = "", Type type = Text);
Type type() const;
QString labelName() const;
QString displayName() const;
private:
Type m_type;
QString m_labelName;
QString m_displayName;
};
class LabelBottle : public QObject
{
Q_OBJECT
Q_PROPERTY(int column READ column NOTIFY columnChanged)
Q_PROPERTY(QList<UkuiMenu::LabelItem> labels READ labels NOTIFY labelsChanged)
public:
explicit LabelBottle(QObject *parent = nullptr);
int column() const;
QList<UkuiMenu::LabelItem> labels() const;
void setColumn(int column);
void setLabels(const QList<UkuiMenu::LabelItem> &labels);
Q_SIGNALS:
void columnChanged();
void labelsChanged();
private:
int m_column {2};
QList<LabelItem> m_labels;
};
class AppListPluginGroup
{
Q_GADGET
@ -52,6 +100,7 @@ public:
virtual QList<QAction*> actions() = 0;
virtual QAbstractItemModel *dataModel() = 0;
virtual void search(const QString &keyword);
virtual LabelBottle *labelBottle();
Q_SIGNALS:
void titleChanged();
@ -60,5 +109,7 @@ Q_SIGNALS:
} // UkuiMenu
Q_DECLARE_METATYPE(UkuiMenu::AppListPluginGroup::Group)
Q_DECLARE_METATYPE(UkuiMenu::LabelItem)
Q_DECLARE_METATYPE(UkuiMenu::LabelBottle*)
#endif //UKUI_MENU_APP_LIST_PLUGIN_H

View File

@ -86,7 +86,7 @@ bool RecentlyInstalledModel::event(QEvent *event)
QVariant RecentlyInstalledModel::data(const QModelIndex &index, int role) const
{
if (role == DataEntity::Group) {
return QStringLiteral("RecentlyInstalled");
return tr("Recently Installed");
}
return QSortFilterProxyModel::data(index, role);

View File

@ -74,6 +74,7 @@ void UkuiMenuApplication::registerQmlTypes()
qmlRegisterUncreatableType<Display>(uri, versionMajor, versionMinor, "Display", "Use enums only.");
qmlRegisterUncreatableType<UkuiMenu::DataType>(uri, versionMajor, versionMinor, "DataType", "Use enums only");
qmlRegisterUncreatableType<UkuiMenu::MenuInfo>(uri, versionMajor, versionMinor, "MenuInfo", "Use enums only.");
qmlRegisterUncreatableType<UkuiMenu::LabelItem>(uri, versionMajor, versionMinor, "LabelItem", "Use enums only.");
// qmlRegisterUncreatableType<UkuiMenu::DataEntity>(uri, versionMajor, versionMinor, "DataEntity", "unknown");
qmlRegisterUncreatableType<EventTrack>(uri, versionMajor, versionMinor, "EventTrack", "Attached only.");