SettingsContent: add collapsible button for tab area

This commit is contained in:
Ly-sec
2025-12-13 15:04:05 +01:00
parent 46cd234604
commit 15526a8336
13 changed files with 214 additions and 82 deletions
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Bildschirmrekorder (Aufnahme starten)",
"click-to-stop-recording": "Bildschirmrekorder (Aufnahme stoppen)",
"close": "Schließen",
"collapse": "Seitenleiste einklappen",
"connect-disconnect-devices": "Bluetooth-Gerät",
"delete-notification": "Benachrichtigung löschen",
"disable-keep-awake": "Wach halten",
"do-not-disturb-disabled": "Nicht stören",
"do-not-disturb-enabled": "Nicht stören",
"enable-keep-awake": "Wach halten",
"expand": "Seitenleiste ausklappen",
"forget-network": "Netzwerk vergessen",
"grid-view": "Rasteransicht",
"hidden-files-hide": "Versteckte Dateien",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Screen recorder (start recording)",
"click-to-stop-recording": "Screen recorder (stop recording)",
"close": "Close",
"collapse": "Collapse sidebar",
"connect-disconnect-devices": "Bluetooth device",
"delete-notification": "Delete notification",
"disable-keep-awake": "Keep Awake",
"do-not-disturb-disabled": "Do Not Disturb",
"do-not-disturb-enabled": "Do Not Disturb",
"enable-keep-awake": "Keep Awake",
"expand": "Expand sidebar",
"forget-network": "Forget network",
"grid-view": "Grid view",
"hidden-files-hide": "Hidden files",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Grabadora de pantalla (iniciar grabación)",
"click-to-stop-recording": "Grabadora de pantalla (detener grabación)",
"close": "Cerrar",
"collapse": "Colapsar barra lateral",
"connect-disconnect-devices": "Dispositivo Bluetooth",
"delete-notification": "Eliminar notificación",
"disable-keep-awake": "Mantener despierto",
"do-not-disturb-disabled": "No molestar",
"do-not-disturb-enabled": "No molestar",
"enable-keep-awake": "Mantener despierto",
"expand": "Expandir barra lateral",
"forget-network": "Olvidar red",
"grid-view": "Vista de cuadrícula",
"hidden-files-hide": "Archivos ocultos",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Enregistreur d'écran (démarrer l'enregistrement)",
"click-to-stop-recording": "Enregistreur d'écran (arrêter l'enregistrement)",
"close": "Fermer",
"collapse": "Réduire la barre latérale",
"connect-disconnect-devices": "Appareil Bluetooth",
"delete-notification": "Supprimer la notification",
"disable-keep-awake": "Rester éveillé",
"do-not-disturb-disabled": "Ne pas déranger",
"do-not-disturb-enabled": "Ne pas déranger",
"enable-keep-awake": "Rester éveillé",
"expand": "Développer la barre latérale",
"forget-network": "Oublier le réseau",
"grid-view": "Vue en grille",
"hidden-files-hide": "Fichiers cachés",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "画面録画 (録画開始)",
"click-to-stop-recording": "画面録画 (録画停止)",
"close": "閉じる",
"collapse": "サイドバーを折りたたむ",
"connect-disconnect-devices": "Bluetooth デバイス",
"delete-notification": "通知を削除",
"disable-keep-awake": "スリープ防止",
"do-not-disturb-disabled": "おやすみモード",
"do-not-disturb-enabled": "おやすみモード",
"enable-keep-awake": "スリープ防止",
"expand": "サイドバーを展開",
"forget-network": "ネットワーク設定を削除",
"grid-view": "グリッド表示",
"hidden-files-hide": "隠しファイル",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Schermrecorder (opname starten)",
"click-to-stop-recording": "Schermrecorder (opname stoppen)",
"close": "Sluiten",
"collapse": "Zijbalk inklappen",
"connect-disconnect-devices": "Bluetooth-apparaat",
"delete-notification": "Melding verwijderen",
"disable-keep-awake": "Wakker houden",
"do-not-disturb-disabled": "Niet storen",
"do-not-disturb-enabled": "Niet storen",
"enable-keep-awake": "Wakker houden",
"expand": "Zijbalk uitklappen",
"forget-network": "Netwerk vergeten",
"grid-view": "Rasterweergave",
"hidden-files-hide": "Verborgen bestanden",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Gravador de tela (iniciar gravação)",
"click-to-stop-recording": "Gravador de tela (parar gravação)",
"close": "Fechar",
"collapse": "Recolher barra lateral",
"connect-disconnect-devices": "Dispositivo Bluetooth",
"delete-notification": "Excluir notificação",
"disable-keep-awake": "Manter acordado",
"do-not-disturb-disabled": "Não perturbe",
"do-not-disturb-enabled": "Não perturbe",
"enable-keep-awake": "Manter acordado",
"expand": "Expandir barra lateral",
"forget-network": "Esquecer rede",
"grid-view": "Visualização em grade",
"hidden-files-hide": "Arquivos ocultos",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Запись экрана (начать запись)",
"click-to-stop-recording": "Запись экрана (остановить запись)",
"close": "Закрыть",
"collapse": "Свернуть боковую панель",
"connect-disconnect-devices": "Устройство Bluetooth",
"delete-notification": "Удалить уведомление",
"disable-keep-awake": "Не засыпать",
"do-not-disturb-disabled": "Не беспокоить",
"do-not-disturb-enabled": "Не беспокоить",
"enable-keep-awake": "Не засыпать",
"expand": "Развернуть боковую панель",
"forget-network": "Забыть сеть",
"grid-view": "Вид сеткой",
"hidden-files-hide": "Скрытые файлы",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Ekran kaydedici (kaydı başlat)",
"click-to-stop-recording": "Ekran kaydedici (kaydı durdur)",
"close": "Kapat",
"collapse": "Kenar çubuğunu daralt",
"connect-disconnect-devices": "Bluetooth cihazı",
"delete-notification": "Bildiriyi sil",
"disable-keep-awake": "Uyanık kal",
"do-not-disturb-disabled": "Rahatsız etme",
"do-not-disturb-enabled": "Rahatsız etme",
"enable-keep-awake": "Uyanık kal",
"expand": "Kenar çubuğunu genişlet",
"forget-network": "Ağı unut",
"grid-view": "Izgara görünümü",
"hidden-files-hide": "Gizli dosyalar",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "Запис екрана (почати запис)",
"click-to-stop-recording": "Запис екрана (зупинити запис)",
"close": "Закрити",
"collapse": "Згорнути бічну панель",
"connect-disconnect-devices": "Пристрій Bluetooth",
"delete-notification": "Видалити сповіщення",
"disable-keep-awake": "Заборона сну",
"do-not-disturb-disabled": "Не турбувати",
"do-not-disturb-enabled": "Не турбувати",
"enable-keep-awake": "Заборона сну",
"expand": "Розгорнути бічну панель",
"forget-network": "Забути мережу",
"grid-view": "Мережевий вигляд",
"hidden-files-hide": "Приховані файли",
+2
View File
@@ -2453,12 +2453,14 @@
"click-to-start-recording": "屏幕录制器(开始录制)",
"click-to-stop-recording": "屏幕录制器(停止录制)",
"close": "关闭",
"collapse": "折叠侧边栏",
"connect-disconnect-devices": "蓝牙设备",
"delete-notification": "删除通知",
"disable-keep-awake": "保持唤醒",
"do-not-disturb-disabled": "勿扰模式",
"do-not-disturb-enabled": "勿扰模式",
"enable-keep-awake": "保持唤醒",
"expand": "展开侧边栏",
"forget-network": "忘记网络",
"grid-view": "网格视图",
"hidden-files-hide": "隐藏文件",
+29 -1
View File
@@ -61,6 +61,11 @@ Singleton {
schemes: [],
timestamp: 0
})
// UI state: settings panel, etc.
property var ui: ({
settingsSidebarExpanded: true
})
}
onLoaded: {
@@ -171,6 +176,28 @@ Singleton {
};
}
// UI state
function setUiState(stateData) {
adapter.ui = stateData;
save();
}
function getUiState() {
return adapter.ui || {
settingsSidebarExpanded: true
};
}
function setSettingsSidebarExpanded(expanded) {
let uiState = getUiState();
uiState.settingsSidebarExpanded = expanded;
setUiState(uiState);
}
function getSettingsSidebarExpanded() {
return getUiState().settingsSidebarExpanded !== false; // default to true
}
// -----------------------------------------------------
function buildStateSnapshot() {
try {
@@ -189,7 +216,8 @@ Singleton {
display: shellStateData.display || {},
notificationsState: shellStateData.notificationsState || {},
changelogState: shellStateData.changelogState || {},
colorSchemesList: shellStateData.colorSchemesList || {}
colorSchemesList: shellStateData.colorSchemesList || {},
ui: shellStateData.ui || {}
}
};
} catch (error) {
+163 -81
View File
@@ -7,6 +7,7 @@ import qs.Modules.Panels.Settings.Tabs
import qs.Modules.Panels.Settings.Tabs.ColorScheme
import qs.Modules.Panels.Settings.Tabs.SessionMenu
import qs.Services.System
import qs.Services.UI
import qs.Widgets
Item {
@@ -19,12 +20,20 @@ Item {
property int currentTabIndex: 0
property var tabsModel: []
property var activeScrollView: null
property bool sidebarExpanded: true
// Signal when close button is clicked
signal closeRequested
// Save sidebar state when it changes
onSidebarExpandedChanged: {
ShellState.setSettingsSidebarExpanded(sidebarExpanded);
}
Component.onCompleted: {
updateTabsModel();
// Restore sidebar state
sidebarExpanded = ShellState.getSettingsSidebarExpanded();
}
// Tab components
@@ -323,120 +332,193 @@ Item {
Rectangle {
id: sidebar
clip: true
Layout.preferredWidth: 200 * Style.uiScaleRatio
Layout.preferredWidth: root.sidebarExpanded ? 200 * Style.uiScaleRatio : sidebarToggle.width
Layout.fillHeight: true
Layout.alignment: Qt.AlignTop
color: Color.transparent
Item {
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
ColumnLayout {
anchors.fill: parent
spacing: Style.marginS
NListView {
id: sidebarList
anchors.fill: parent
model: root.tabsModel
spacing: Style.marginXS
currentIndex: root.currentTabIndex
verticalPolicy: ScrollBar.AsNeeded
// Sidebar toggle button
Item {
id: toggleContainer
Layout.fillWidth: true
Layout.preferredHeight: toggleRow.implicitHeight + Style.marginS * 2
delegate: Rectangle {
id: tabItem
width: sidebarList.verticalScrollBarActive ? sidebarList.width - sidebarList.scrollBarWidth - Style.marginXS : sidebarList.width
height: tabEntryRow.implicitHeight + Style.marginS * 2
Rectangle {
id: sidebarToggle
width: toggleRow.implicitWidth + Style.marginS * 2
height: parent.height
anchors.left: parent.left
radius: Style.radiusS
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : Color.transparent)
readonly property bool selected: index === root.currentTabIndex
property bool hovering: false
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnHover : Color.mOnSurface)
Behavior on width {
NumberAnimation {
duration: Style.animationFast
}
}
color: toggleMouseArea.containsMouse ? Color.mHover : Color.transparent
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on tabTextColor {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
RowLayout {
id: tabEntryRow
anchors.fill: parent
id: toggleRow
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Style.marginS
anchors.rightMargin: Style.marginS
spacing: Style.marginM
spacing: 0
NIcon {
icon: modelData.icon
color: tabTextColor
icon: root.sidebarExpanded ? "layout-sidebar-left-expand" : "layout-sidebar-right-expand"
color: Color.mOnSurface
pointSize: Style.fontSizeXL
}
NText {
text: I18n.tr(modelData.label)
color: tabTextColor
pointSize: Style.fontSizeM
font.weight: Style.fontWeightSemiBold
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
}
MouseArea {
id: toggleMouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onEntered: tabItem.hovering = true
onExited: tabItem.hovering = false
onCanceled: tabItem.hovering = false
onClicked: root.currentTabIndex = index
}
}
onCurrentIndexChanged: {
if (currentIndex !== root.currentTabIndex) {
root.currentTabIndex = currentIndex;
}
}
Connections {
target: root
function onCurrentTabIndexChanged() {
if (sidebarList.currentIndex !== root.currentTabIndex) {
sidebarList.currentIndex = root.currentTabIndex;
sidebarList.positionViewAtIndex(root.currentTabIndex, ListView.Contain);
cursorShape: Qt.PointingHandCursor
onEntered: {
TooltipService.show(sidebarToggle, root.sidebarExpanded ? I18n.tr("tooltips.collapse") : I18n.tr("tooltips.expand"));
}
onExited: {
TooltipService.hide();
}
onClicked: {
TooltipService.hide();
root.sidebarExpanded = !root.sidebarExpanded;
}
}
}
}
// Overlay gradient for sidebar scrolling
Rectangle {
anchors.fill: parent
anchors.margins: Style.borderS
radius: Style.radiusM
color: Color.transparent
visible: sidebarList.verticalScrollBarActive
gradient: Gradient {
GradientStop {
position: 0.0
color: Color.transparent
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NListView {
id: sidebarList
anchors.fill: parent
model: root.tabsModel
spacing: Style.marginXS
currentIndex: root.currentTabIndex
verticalPolicy: ScrollBar.AsNeeded
delegate: Rectangle {
id: tabItem
width: sidebarList.verticalScrollBarActive ? sidebarList.width - sidebarList.scrollBarWidth - Style.marginXS : sidebarList.width
height: tabEntryRow.implicitHeight + Style.marginS * 2
radius: Style.radiusS
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : Color.transparent)
readonly property bool selected: index === root.currentTabIndex
property bool hovering: false
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnHover : Color.mOnSurface)
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
Behavior on tabTextColor {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
RowLayout {
id: tabEntryRow
anchors.fill: parent
anchors.leftMargin: Style.marginS
anchors.rightMargin: Style.marginS
spacing: Style.marginM
NIcon {
icon: modelData.icon
color: tabTextColor
pointSize: Style.fontSizeXL
Layout.alignment: Qt.AlignVCenter
}
NText {
text: I18n.tr(modelData.label)
color: tabTextColor
pointSize: Style.fontSizeM
font.weight: Style.fontWeightSemiBold
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
visible: root.sidebarExpanded
opacity: root.sidebarExpanded ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onEntered: tabItem.hovering = true
onExited: tabItem.hovering = false
onCanceled: tabItem.hovering = false
onClicked: root.currentTabIndex = index
}
}
GradientStop {
position: 0.95
color: Color.transparent
onCurrentIndexChanged: {
if (currentIndex !== root.currentTabIndex) {
root.currentTabIndex = currentIndex;
}
}
GradientStop {
position: 1.0
color: Color.mSurfaceVariant
Connections {
target: root
function onCurrentTabIndexChanged() {
if (sidebarList.currentIndex !== root.currentTabIndex) {
sidebarList.currentIndex = root.currentTabIndex;
sidebarList.positionViewAtIndex(root.currentTabIndex, ListView.Contain);
}
}
}
}
// Overlay gradient for sidebar scrolling
Rectangle {
anchors.fill: parent
anchors.margins: Style.borderS
radius: Style.radiusM
color: Color.transparent
visible: sidebarList.verticalScrollBarActive
gradient: Gradient {
GradientStop {
position: 0.0
color: Color.transparent
}
GradientStop {
position: 0.95
color: Color.transparent
}
GradientStop {
position: 1.0
color: Color.mSurfaceVariant
}
}
}
}