From a08696476964d5c31b0bf6b8c9ff4c605ee167e6 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Jun 2021 12:50:09 +0000 Subject: [PATCH] Make sidebar collapsible --- imports/NeoChat/Page/RoomListPage.qml | 251 ++++++++++++++++++-------- qml/main.qml | 65 ++++++- src/neochatconfig.kcfg | 3 + src/roomlistmodel.cpp | 18 ++ src/roomlistmodel.h | 1 + 5 files changed, 260 insertions(+), 78 deletions(-) diff --git a/imports/NeoChat/Page/RoomListPage.qml b/imports/NeoChat/Page/RoomListPage.qml index 1f2107ef8..6bd134813 100644 --- a/imports/NeoChat/Page/RoomListPage.qml +++ b/imports/NeoChat/Page/RoomListPage.qml @@ -18,6 +18,24 @@ Kirigami.ScrollablePage { id: page property var enteredRoom + property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars + + onCollapsedModeChanged: if (collapsedMode) { + sortFilterRoomListModel.filterText = ""; + if (page.contentItem && page.contentItem.flickableItem && page.contentItem.flickableItem.QQC2.ScrollBar.vertical) { + page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = false; + } + } else { + page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = true; + } + + // HACK: the scrollbar is created with a 0 timer, so we need to set the visible flag + // after it has been created + Timer { + running: true + interval: 200 + onTriggered: page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = !collapsedMode; + } function goToNextRoom() { do { @@ -33,17 +51,51 @@ Kirigami.ScrollablePage { listView.currentItem.action.trigger(); } - title: i18n("Rooms") + titleDelegate: collapsedMode ? empty : searchField - titleDelegate: Kirigami.SearchField { - Layout.topMargin: Kirigami.Units.smallSpacing - Layout.bottomMargin: Kirigami.Units.smallSpacing - Layout.fillHeight: true - Layout.fillWidth: true - onTextChanged: sortFilterRoomListModel.filterText = text - KeyNavigation.tab: listView + Component { + id: empty + Item {} } + Component { + id: searchField + Kirigami.SearchField { + Layout.topMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing + Layout.fillHeight: true + Layout.fillWidth: true + onTextChanged: sortFilterRoomListModel.filterText = text + KeyNavigation.tab: listView + } + } + + header: QQC2.ItemDelegate { + visible: page.collapsedMode + action: Kirigami.Action { + id: enterRoomAction + onTriggered: quickView.item.open(); + } + topPadding: Kirigami.Units.largeSpacing + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + width: visible ? page.width : 0 + height: visible ? Kirigami.Units.gridUnit * 2 : 0 + + Kirigami.Icon { + anchors.centerIn: parent + width: 22 * Kirigami.Units.devicePixelRatio + height: 22 * Kirigami.Units.devicePixelRatio + source: "search" + } + Kirigami.Separator { + width: parent.width + anchors.bottom: parent.bottom + } + } + + ListView { id: listView @@ -64,6 +116,7 @@ Kirigami.ScrollablePage { } } + ItemSelectionModel { id: itemSelection model: roomListModel @@ -97,11 +150,14 @@ Kirigami.ScrollablePage { level: 3 text: roomListModel.categoryName(section) Layout.fillWidth: true + elide: Text.ElideRight + visible: !page.collapsedMode } Kirigami.Icon { - source: roomListModel.categoryVisible(section) ? "go-up" : "go-down" + source: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down") implicitHeight: Kirigami.Units.iconSizes.small implicitWidth: Kirigami.Units.iconSizes.small + Layout.alignment: Qt.AlignHCenter } } } @@ -109,87 +165,128 @@ Kirigami.ScrollablePage { reuseItems: true currentIndex: -1 // we don't want any room highlighted by default - delegate: Kirigami.BasicListItem { - id: roomListItem - visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList - topPadding: Kirigami.Units.largeSpacing - bottomPadding: Kirigami.Units.largeSpacing - highlighted: listView.currentIndex === index - focus: true - icon: undefined - action: Kirigami.Action { - id: enterRoomAction - onTriggered: { - RoomManager.enterRoom(currentRoom); - itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource( - sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent) - } - } - Keys.onEnterPressed: enterRoomAction.trigger() - Keys.onReturnPressed: enterRoomAction.trigger() - bold: unreadCount > 0 - label: name ?? "" - subtitle: { - let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ") - if (txt.length) { - return txt - } - return " " - } + delegate: page.collapsedMode ? collapsedModeListComponent : normalModeListComponent - leading: Kirigami.Avatar { - source: avatar ? "image://mxc/" + avatar : "" - name: model.name || i18n("No Name") - implicitWidth: visible ? height : 0 - visible: Config.showAvatarInTimeline - sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - } + Component { + id: collapsedModeListComponent - trailing: RowLayout { - QQC2.Label { - text: notificationCount - visible: notificationCount > 0 - padding: Kirigami.Units.smallSpacing - color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor - Layout.minimumWidth: height - horizontalAlignment: Text.AlignHCenter - background: Rectangle { - Kirigami.Theme.colorSet: Kirigami.Theme.Button - color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor - radius: height / 2 + QQC2.ItemDelegate { + action: Kirigami.Action { + id: enterRoomAction + onTriggered: { + RoomManager.enterRoom(currentRoom); + itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource( + sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent) } } - QQC2.Button { - id: configButton - visible: roomListItem.hovered || Kirigami.Settings.isMobile - Accessible.name: i18n("Configure room") + Keys.onEnterPressed: enterRoomAction.trigger() + Keys.onReturnPressed: enterRoomAction.trigger() + topPadding: Kirigami.Units.largeSpacing + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + width: ListView.view.width + height: ListView.view.width - action: Kirigami.Action { - id: optionAction - icon.name: "configure" - onTriggered: { - const menu = roomListContextMenu.createObject(page, {"room": currentRoom}) - configButton.visible = true - configButton.down = true - menu.closed.connect(function() { - configButton.down = undefined - configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile }) - }) - menu.popup() + contentItem: Kirigami.Avatar { + source: avatar ? "image://mxc/" + avatar : "" + name: model.name || i18n("No Name") + sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + } + + QQC2.ToolTip { + enabled: text.length !== 0 + text: name ?? "" + } + } + } + + Component { + id: roomListContextMenu + RoomListContextMenu {} + } + + Component { + id: normalModeListComponent + Kirigami.BasicListItem { + id: roomListItem + visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList + topPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + highlighted: listView.currentIndex === index + focus: true + icon: undefined + action: Kirigami.Action { + id: enterRoomAction + onTriggered: { + RoomManager.enterRoom(currentRoom); + itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource( + sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent) + } + } + Keys.onEnterPressed: enterRoomAction.trigger() + Keys.onReturnPressed: enterRoomAction.trigger() + bold: unreadCount > 0 + label: name ?? "" + subtitle: { + let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ") + if (txt.length) { + return txt + } + return " " + } + + leading: Kirigami.Avatar { + source: avatar ? "image://mxc/" + avatar : "" + name: model.name || i18n("No Name") + implicitWidth: visible ? height : 0 + visible: Config.showAvatarInTimeline + sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + } + + trailing: RowLayout { + QQC2.Label { + text: notificationCount + visible: notificationCount > 0 + padding: Kirigami.Units.smallSpacing + color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor + Layout.minimumWidth: height + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + Kirigami.Theme.colorSet: Kirigami.Theme.Button + color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor + radius: height / 2 + } + } + QQC2.Button { + id: configButton + visible: roomListItem.hovered || Kirigami.Settings.isMobile + Accessible.name: i18n("Configure room") + + action: Kirigami.Action { + id: optionAction + icon.name: "configure" + onTriggered: { + const menu = roomListContextMenu.createObject(page, {"room": currentRoom}) + configButton.visible = true + configButton.down = true + menu.closed.connect(function() { + configButton.down = undefined + configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile }) + }) + menu.popup() + } } } } } } - Component { - id: roomListContextMenu - RoomListContextMenu {} - } } footer: QQC2.ToolBar { - visible: accountList.count > 1 + visible: accountList.count > 1 && !collapsedMode height: visible ? implicitHeight : 0 leftPadding: 0 rightPadding: 0 diff --git a/qml/main.qml b/qml/main.qml index 0401ab054..d01448565 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -166,7 +166,70 @@ Kirigami.ApplicationWindow { handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3 } - pageStack.columnView.columnWidth: Kirigami.Units.gridUnit * 17 + readonly property int defaultPageWidth: Kirigami.Units.gridUnit * 17 + readonly property int minPageWidth: Kirigami.Units.gridUnit * 10 + readonly property int collapsedPageWidth: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3 + readonly property bool shouldUseSidebars: (Config.roomListPageWidth > minPageWidth ? root.width >= Kirigami.Units.gridUnit * 35 : root.width > Kirigami.Units.gridUnit * 27) && roomListLoaded + readonly property int pageWidth: { + if (Config.roomListPageWidth === -1) { + return defaultPageWidth; + } else if (Config.roomListPageWidth < minPageWidth) { + return collapsedPageWidth; + } else { + return Config.roomListPageWidth; + } + } + + pageStack.defaultColumnWidth: pageWidth + pageStack.columnView.columnResizeMode: shouldUseSidebars ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn + + MouseArea { + visible: root.pageStack.wideMode + z: 500 + + anchors.top: parent.top + anchors.bottom: parent.bottom + + x: root.pageStack.defaultColumnWidth - (width / 2) + width: Kirigami.Units.devicePixelRatio * 2 + + property int _lastX: -1 + enabled: !Kirigami.Settings.isMobile + + cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined + + onPressed: _lastX = mouseX + onReleased: Config.save(); + + onPositionChanged: { + if (_lastX == -1) { + return; + } + + if (mouse.x > _lastX) { + // we moved to the right + if (Config.roomListPageWidth === root.collapsedPageWidth && root.pageWidth + (mouse.x - _lastX) >= root.minPageWidth) { + // Here we get back directly to a more wide mode. + Config.roomListPageWidth = root.minPageWidth; + if (root.width < Kirigami.Units.gridUnit * 35) { + root.width = Kirigami.Units.gridUnit * 35; + } + } else if (Config.roomListPageWidth !== root.collapsedPageWidth) { + // Increase page width + Config.roomListPageWidth = Math.min(root.defaultPageWidth, root.pageWidth + (mouse.x - _lastX)); + } + } else if (mouse.x < _lastX) { + const tmpWidth = root.pageWidth - (_lastX - mouse.x); + + if (tmpWidth < root.minPageWidth) { + Config.roomListPageWidth = root.collapsedPageWidth; + } else { + Config.roomListPageWidth = tmpWidth; + } + } + } + } + globalDrawer: Kirigami.GlobalDrawer { property bool hasLayer diff --git a/src/neochatconfig.kcfg b/src/neochatconfig.kcfg index adccfb73c..8acba0e2a 100644 --- a/src/neochatconfig.kcfg +++ b/src/neochatconfig.kcfg @@ -41,6 +41,9 @@ true + + -1 + diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 856a330ff..c35e96a5b 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -432,6 +432,24 @@ QString RoomListModel::categoryName(int section) } } +QString RoomListModel::categoryIconName(int section) +{ + switch (section) { + case 1: + return QStringLiteral("user-invisible"); + case 2: + return QStringLiteral("favorite"); + case 3: + return QStringLiteral("dialog-messages"); + case 4: + return QStringLiteral("group"); + case 5: + return QStringLiteral("object-order-lower"); + default: + return QStringLiteral("tools-report-bug"); + } +} + void RoomListModel::setCategoryVisible(int category, bool visible) { beginResetModel(); diff --git a/src/roomlistmodel.h b/src/roomlistmodel.h index 924f93caa..4bcb06142 100644 --- a/src/roomlistmodel.h +++ b/src/roomlistmodel.h @@ -69,6 +69,7 @@ public: [[nodiscard]] QHash roleNames() const override; Q_INVOKABLE [[nodiscard]] static QString categoryName(int section); + Q_INVOKABLE [[nodiscard]] static QString categoryIconName(int section); Q_INVOKABLE void setCategoryVisible(int category, bool visible); Q_INVOKABLE [[nodiscard]] bool categoryVisible(int category) const;