mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
ControlCenter: modularity!
This commit is contained in:
@@ -730,14 +730,16 @@
|
||||
"label": "Aussehen"
|
||||
},
|
||||
"title": "Kontrollzentrum",
|
||||
"audio-controls": {
|
||||
"description": "Verschiedene Audio-Lautstärken sofort über das Kontrollzentrum anpassen.",
|
||||
"label": "Lautstärkeregler"
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Konfigurieren und verwalten Sie die Schnellzugriff-Widgets.",
|
||||
"label": "Schnelleinstellungen-Widgets"
|
||||
"description": "Passen Sie an, welche Steuerelemente im Kontrollzentrum angezeigt werden und in welcher Reihenfolge.",
|
||||
"label": "Karten"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"description": "Konfigurieren und verwalten Sie die Verknüpfungs-Widgets.",
|
||||
"label": "Widgets für Kurzbefehle"
|
||||
},
|
||||
"sectionLeft": "Links",
|
||||
"sectionRight": "Richtig"
|
||||
|
||||
@@ -731,17 +731,19 @@
|
||||
"label": "Position",
|
||||
"description": "Choose where the Control Center panel appears when opened."
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"label": "Quick settings widgets",
|
||||
"description": "Configure and manage the quick settings widgets."
|
||||
"label": "Cards",
|
||||
"description": "Customize which controls appear in the Control Center and in what order."
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"label": "Shortcuts widgets",
|
||||
"description": "Configure and manage the shortcuts widgets."
|
||||
},
|
||||
"sectionLeft": "Left",
|
||||
"sectionRight": "Right"
|
||||
},
|
||||
"audio-controls": {
|
||||
"label": "Volume sliders",
|
||||
"description": "Instantly adjust different audio volumes from the Control Center."
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -730,14 +730,16 @@
|
||||
"label": "Apariencia"
|
||||
},
|
||||
"title": "Centro de control",
|
||||
"audio-controls": {
|
||||
"description": "Ajusta instantáneamente diferentes volúmenes de audio desde el Centro de control.",
|
||||
"label": "Deslizadores de volumen"
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Configurar y administrar los widgets de configuración rápida.",
|
||||
"label": "Widgets de configuración rápida"
|
||||
"description": "Personaliza qué controles aparecen en el Centro de control y en qué orden.",
|
||||
"label": "Tarjetas"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"description": "Configurar y administrar los widgets de accesos directos.",
|
||||
"label": "Widgets de accesos directos"
|
||||
},
|
||||
"sectionLeft": "Izquierda",
|
||||
"sectionRight": "Derecha"
|
||||
|
||||
@@ -730,14 +730,16 @@
|
||||
"label": "Apparence"
|
||||
},
|
||||
"title": "Centre de contrôle",
|
||||
"audio-controls": {
|
||||
"description": "Ajustez instantanément différents volumes audio depuis le Centre de contrôle.",
|
||||
"label": "Curseurs de volume"
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Configurer et gérer les widgets des paramètres rapides.",
|
||||
"label": "Widgets de paramètres rapides"
|
||||
"description": "Personnalisez les commandes qui apparaissent dans le Centre de contrôle et leur ordre d'affichage.",
|
||||
"label": "Cartes"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"description": "Configurer et gérer les widgets de raccourcis.",
|
||||
"label": "Widgets de raccourcis"
|
||||
},
|
||||
"sectionLeft": "Gauche",
|
||||
"sectionRight": "Droite"
|
||||
|
||||
@@ -730,17 +730,19 @@
|
||||
"label": "Aparência"
|
||||
},
|
||||
"title": "Central de Controle",
|
||||
"audio-controls": {
|
||||
"description": "Ajuste instantaneamente diferentes volumes de áudio a partir da Central de Controle.",
|
||||
"label": "Controles deslizantes de volume"
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Configure e gerencie os widgets de configurações rápidas.",
|
||||
"label": "Widgets de configurações rápidas"
|
||||
"description": "Personalize quais controles aparecem na Central de Controle e em que ordem.",
|
||||
"label": "Cartas"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"description": "Configure e gerencie os widgets de atalhos.",
|
||||
"label": "Widgets de atalhos"
|
||||
},
|
||||
"sectionLeft": "Esquerda",
|
||||
"sectionRight": "Direito/Certo/Correto"
|
||||
"sectionRight": "Direito/Certo/À direita"
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -730,14 +730,16 @@
|
||||
"label": "外观"
|
||||
},
|
||||
"title": "控制中心",
|
||||
"audio-controls": {
|
||||
"description": "从控制中心即时调整不同的音频音量。",
|
||||
"label": "音量滑块"
|
||||
},
|
||||
"quick-settings": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "配置和管理快速设置小部件。",
|
||||
"label": "快速设置小部件"
|
||||
"description": "自定义在控制中心显示的控制项及其顺序。",
|
||||
"label": "卡片"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"section": {
|
||||
"description": "配置和管理快捷方式小部件。",
|
||||
"label": "快捷方式小部件"
|
||||
},
|
||||
"sectionLeft": "左",
|
||||
"sectionRight": "右"
|
||||
|
||||
+15
-7
@@ -159,10 +159,6 @@ Singleton {
|
||||
"id": "Tray"
|
||||
}, {
|
||||
"id": "NotificationHistory"
|
||||
}, {
|
||||
"id": "WiFi"
|
||||
}, {
|
||||
"id": "Bluetooth"
|
||||
}, {
|
||||
"id": "Battery"
|
||||
}, {
|
||||
@@ -245,9 +241,8 @@ Singleton {
|
||||
property JsonObject controlCenter: JsonObject {
|
||||
// Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
|
||||
property string position: "close_to_bar_button"
|
||||
property bool audioControlsEnabled: true
|
||||
property JsonObject widgets
|
||||
widgets: JsonObject {
|
||||
property JsonObject shortcuts
|
||||
shortcuts: JsonObject {
|
||||
property list<var> left: [{
|
||||
"id": "WiFi"
|
||||
}, {
|
||||
@@ -263,6 +258,19 @@ Singleton {
|
||||
"id": "WallpaperSelector"
|
||||
}]
|
||||
}
|
||||
property list<var> cards: [{
|
||||
"id": "profile-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
"id": "shortcuts-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
"id": "audio-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
"id": "media-sysmon-card",
|
||||
"enabled": true
|
||||
}]
|
||||
}
|
||||
|
||||
// dock
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
// Power Profiles: performance, balanced, eco
|
||||
NBox {
|
||||
|
||||
property real spacing: 0
|
||||
|
||||
// Centralized service
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
|
||||
RowLayout {
|
||||
id: powerRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: spacing
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
// Performance
|
||||
NIconButton {
|
||||
icon: PowerProfileService.getIcon(PowerProfile.Performance)
|
||||
tooltipText: I18n.tr("tooltips.set-power-profile", {
|
||||
"profile": PowerProfileService.getName(PowerProfile.Performance)
|
||||
})
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.Performance)
|
||||
}
|
||||
// Balanced
|
||||
NIconButton {
|
||||
icon: PowerProfileService.getIcon(PowerProfile.Balanced)
|
||||
tooltipText: I18n.tr("tooltips.set-power-profile", {
|
||||
"profile": PowerProfileService.getName(PowerProfile.Balanced)
|
||||
})
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.Balanced)
|
||||
}
|
||||
// Eco
|
||||
NIconButton {
|
||||
icon: PowerProfileService.getIcon(PowerProfile.PowerSaver)
|
||||
tooltipText: I18n.tr("tooltips.set-power-profile", {
|
||||
"profile": PowerProfileService.getName(PowerProfile.PowerSaver)
|
||||
})
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Modules.ControlCenter.Cards
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Extras
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.shortcutsHeight
|
||||
|
||||
RowLayout {
|
||||
id: leftContent
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.controlCenter.shortcuts.left
|
||||
delegate: ControlCenterWidgetLoader {
|
||||
Layout.fillWidth: false
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"widgetId": modelData.id,
|
||||
"section": "quickSettings",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.left.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.shortcutsHeight
|
||||
|
||||
RowLayout {
|
||||
id: rightContent
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.controlCenter.shortcuts.right
|
||||
delegate: ControlCenterWidgetLoader {
|
||||
Layout.fillWidth: false
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"widgetId": modelData.id,
|
||||
"section": "quickSettings",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.right.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Settings
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
// Utilities: record & wallpaper
|
||||
NBox {
|
||||
|
||||
property real spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: utilRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: spacing
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
// Screen Recorder
|
||||
NIconButton {
|
||||
icon: "camera-video"
|
||||
enabled: ScreenRecorderService.isAvailable
|
||||
tooltipText: ScreenRecorderService.isAvailable ? (ScreenRecorderService.isRecording ? I18n.tr("tooltips.stop-screen-recording") : I18n.tr("tooltips.start-screen-recording")) : I18n.tr("tooltips.screen-recorder-not-installed")
|
||||
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (!ScreenRecorderService.isAvailable)
|
||||
return
|
||||
ScreenRecorderService.toggleRecording()
|
||||
// If we were not recording and we just initiated a start, close the panel
|
||||
if (!ScreenRecorderService.isRecording) {
|
||||
var panel = PanelService.getPanel("controlCenterPanel")
|
||||
panel?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Idle Inhibitor
|
||||
NIconButton {
|
||||
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
|
||||
tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake")
|
||||
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
IdleInhibitorService.manualToggle()
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper
|
||||
NIconButton {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: I18n.tr("tooltips.wallpaper-selector")
|
||||
onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
|
||||
onRightClicked: WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,33 @@ NPanel {
|
||||
panelKeyboardFocus: true
|
||||
preferredWidth: Math.round(460 * Style.uiScaleRatio)
|
||||
preferredHeight: {
|
||||
let height = profileHeight + weatherHeight + mediaSysMonHeight + utilsHeight
|
||||
let count = 4
|
||||
if (Settings.data.controlCenter.audioControlsEnabled) {
|
||||
var height = 0
|
||||
var count = 0
|
||||
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
|
||||
const card = Settings.data.controlCenter.cards[i]
|
||||
if (!card.enabled) {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
height += audioHeight
|
||||
switch (card.id) {
|
||||
case "profile-card":
|
||||
height += profileHeight
|
||||
break
|
||||
case "shortcuts-card":
|
||||
height += shortcutsHeight
|
||||
break
|
||||
case "audio-card":
|
||||
height += audioHeight
|
||||
break
|
||||
case "weather-card":
|
||||
height += weatherHeight
|
||||
break
|
||||
case "media-sysmon-card":
|
||||
height += mediaSysMonHeight
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return height + (count + 1) * Style.marginL
|
||||
}
|
||||
@@ -32,15 +54,14 @@ NPanel {
|
||||
panelAnchorTop: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("top_")
|
||||
|
||||
readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio)
|
||||
readonly property int shortcutsHeight: Math.round(52 * Style.uiScaleRatio)
|
||||
readonly property int audioHeight: Math.round(120 * Style.uiScaleRatio)
|
||||
readonly property int weatherHeight: Math.round(190 * Style.uiScaleRatio)
|
||||
readonly property int mediaSysMonHeight: Math.round(260 * Style.uiScaleRatio)
|
||||
readonly property int audioHeight: Math.round(120 * Style.uiScaleRatio)
|
||||
readonly property int utilsHeight: Math.round(52 * Style.uiScaleRatio)
|
||||
|
||||
panelContent: Item {
|
||||
id: content
|
||||
|
||||
// Layout content
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
x: Style.marginL
|
||||
@@ -48,50 +69,69 @@ NPanel {
|
||||
width: parent.width - (Style.marginL * 2)
|
||||
spacing: Style.marginL
|
||||
|
||||
// Profile
|
||||
ProfileCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: profileHeight
|
||||
}
|
||||
|
||||
// Utils
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: utilsHeight
|
||||
spacing: Style.marginL
|
||||
|
||||
// Power Profiles switcher
|
||||
PowerProfilesCard {
|
||||
Repeater {
|
||||
model: Settings.data.controlCenter.cards
|
||||
Loader {
|
||||
active: modelData.enabled
|
||||
visible: active
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: Style.marginL
|
||||
}
|
||||
|
||||
// Utilities buttons
|
||||
UtilitiesCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: Style.marginL
|
||||
Layout.preferredHeight: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileHeight
|
||||
case "shortcuts-card":
|
||||
return shortcutsHeight
|
||||
case "audio-card":
|
||||
return audioHeight
|
||||
case "weather-card":
|
||||
return weatherHeight
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonHeight
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
sourceComponent: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileCard
|
||||
case "shortcuts-card":
|
||||
return shortcutsCard
|
||||
case "audio-card":
|
||||
return audioCard
|
||||
case "weather-card":
|
||||
return weatherCard
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonCard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio controls
|
||||
AudioCard {
|
||||
visible: Settings.data.controlCenter.audioControlsEnabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: audioHeight
|
||||
}
|
||||
Component {
|
||||
id: profileCard
|
||||
ProfileCard {}
|
||||
}
|
||||
|
||||
// Weather
|
||||
WeatherCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: weatherHeight
|
||||
}
|
||||
Component {
|
||||
id: shortcutsCard
|
||||
ShortcutsCard {}
|
||||
}
|
||||
|
||||
// Media + SysMon
|
||||
Component {
|
||||
id: audioCard
|
||||
AudioCard {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: weatherCard
|
||||
WeatherCard {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mediaSysMonCard
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: mediaSysMonHeight
|
||||
spacing: Style.marginL
|
||||
|
||||
// Media card
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
enabled: ProgramCheckerService.wlsunsetAvailable
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
// Performance
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
|
||||
@@ -4,18 +4,13 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
enabled: ProgramCheckerService.gpuScreenRecorderAvailable
|
||||
icon: "camera-video"
|
||||
hot: ScreenRecorderService.isRecording
|
||||
tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action")
|
||||
|
||||
// Force hover state when recording to get hover colors
|
||||
property bool originalHovered: hovered
|
||||
hovered: ScreenRecorderService.isRecording || originalHovered
|
||||
|
||||
onClicked: {
|
||||
ScreenRecorderService.toggleRecording()
|
||||
if (!ScreenRecorderService.isRecording) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
enabled: Settings.data.wallpaper.enabled
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NQuickSetting {
|
||||
NIconButtonHot {
|
||||
property ShellScreen screen
|
||||
|
||||
icon: {
|
||||
|
||||
@@ -14,11 +14,13 @@ NBox {
|
||||
property var widgetModel: []
|
||||
property var availableWidgets: []
|
||||
property bool enableMoveBetweenSections: true
|
||||
property int maxWidgets: -1 // -1 means unlimited
|
||||
|
||||
property var widgetRegistry: null
|
||||
property string settingsDialogComponent: "BarWidgetSettingsDialog.qml"
|
||||
|
||||
readonly property real miniButtonSize: Style.baseWidgetSize * 0.65
|
||||
readonly property bool isAtMaxCapacity: maxWidgets > 0 && widgetModel.length >= maxWidgets
|
||||
|
||||
signal addWidget(string widgetId, string section)
|
||||
signal removeWidget(string section, int index)
|
||||
@@ -80,6 +82,16 @@ NBox {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Widget count indicator (when max is set)
|
||||
NText {
|
||||
visible: root.maxWidgets > 0
|
||||
text: "(" + widgetModel.length + "/" + root.maxWidgets + ")"
|
||||
pointSize: Style.fontSizeS
|
||||
color: root.isAtMaxCapacity ? Color.mError : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginXS
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -93,6 +105,7 @@ NBox {
|
||||
onSelected: key => comboBox.currentKey = key
|
||||
popupHeight: 340
|
||||
minimumWidth: 200
|
||||
enabled: !root.isAtMaxCapacity
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
@@ -114,12 +127,14 @@ NBox {
|
||||
colorFg: Color.mOnPrimary
|
||||
colorBgHover: Color.mSecondary
|
||||
colorFgHover: Color.mOnSecondary
|
||||
enabled: comboBox.currentKey !== ""
|
||||
tooltipText: I18n.tr("tooltips.add-widget")
|
||||
enabled: comboBox.currentKey !== "" && !root.isAtMaxCapacity
|
||||
tooltipText: root.isAtMaxCapacity
|
||||
? I18n.tr("tooltips.max-widgets-reached")
|
||||
: I18n.tr("tooltips.add-widget")
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS
|
||||
onClicked: {
|
||||
if (comboBox.currentKey !== "") {
|
||||
if (comboBox.currentKey !== "" && !root.isAtMaxCapacity) {
|
||||
addWidget(comboBox.currentKey, sectionId)
|
||||
comboBox.currentKey = ""
|
||||
}
|
||||
@@ -588,4 +603,4 @@ NBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,34 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
property list<var> cardsModel: []
|
||||
property list<var> cardsDefault: [{
|
||||
"id": "profile-card",
|
||||
"text": "Profile",
|
||||
"enabled": true,
|
||||
"required": true
|
||||
}, {
|
||||
"id": "shortcuts-card",
|
||||
"text": "Shortcuts",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}, {
|
||||
"id": "weather-card",
|
||||
"text": "Weather",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}, {
|
||||
"id": "audio-card",
|
||||
"text": "Audio Sliders",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}, {
|
||||
"id": "media-sysmon-card",
|
||||
"text": "Media and System Monitor",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}]
|
||||
|
||||
// Handler for drag start - disables panel background clicks
|
||||
function handleDragStart() {
|
||||
var panel = PanelService.getPanel("settingsPanel")
|
||||
@@ -27,6 +55,60 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
function saveCards() {
|
||||
var toSave = []
|
||||
for (var i = 0; i < cardsModel.length; i++) {
|
||||
toSave.push({
|
||||
"id": cardsModel[i].id,
|
||||
"enabled": cardsModel[i].enabled
|
||||
})
|
||||
}
|
||||
Settings.data.controlCenter.cards = toSave
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Fill out availableWidgets ListModel
|
||||
availableWidgets.clear()
|
||||
ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => {
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": entry
|
||||
})
|
||||
})
|
||||
// Starts empty
|
||||
cardsModel = []
|
||||
|
||||
// Add the cards available in settings
|
||||
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
|
||||
const settingCard = Settings.data.controlCenter.cards[i]
|
||||
|
||||
for (var j = 0; j < cardsDefault.length; j++) {
|
||||
if (settingCard.id === cardsDefault[j].id) {
|
||||
var card = cardsDefault[j]
|
||||
card.enabled = settingCard.enabled
|
||||
cardsModel.push(card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any missing cards from default
|
||||
for (var i = 0; i < cardsDefault.length; i++) {
|
||||
var found = false
|
||||
for (var j = 0; j < cardsModel.length; j++) {
|
||||
if (cardsModel[j].id === cardsDefault[i].id) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
cardsModel.push(cardsDefault[i])
|
||||
}
|
||||
}
|
||||
|
||||
saveCards()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
@@ -70,11 +152,49 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.control-center.audio-controls.label")
|
||||
description: I18n.tr("settings.control-center.audio-controls.description")
|
||||
checked: Settings.data.controlCenter.audioControlsEnabled
|
||||
onToggled: checked => Settings.data.controlCenter.audioControlsEnabled = checked
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL
|
||||
Layout.bottomMargin: Style.marginXL
|
||||
}
|
||||
|
||||
// Widgets Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.control-center.cards.section.label")
|
||||
description: I18n.tr("settings.control-center.cards.section.description")
|
||||
}
|
||||
|
||||
NReorderCheckboxes {
|
||||
Layout.fillWidth: true
|
||||
model: cardsModel
|
||||
onDragPotentialStarted: {
|
||||
root.handleDragStart()
|
||||
}
|
||||
onDragPotentialEnded: {
|
||||
root.handleDragEnd()
|
||||
}
|
||||
onItemToggled: function (index, enabled) {
|
||||
//Logger.log("ControlCenterTab", "Item", index, "toggled to", enabled)
|
||||
var newModel = cardsModel.slice()
|
||||
newModel[index] = Object.assign({}, newModel[index], {
|
||||
"enabled": enabled
|
||||
})
|
||||
cardsModel = newModel
|
||||
saveCards()
|
||||
}
|
||||
onItemsReordered: function (fromIndex, toIndex) {
|
||||
//Logger.log("ControlCenterTab", "Item moved from", fromIndex, "to", toIndex)
|
||||
var newModel = cardsModel.slice()
|
||||
var item = newModel.splice(fromIndex, 1)[0]
|
||||
newModel.splice(toIndex, 0, item)
|
||||
cardsModel = newModel
|
||||
saveCards()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
@@ -83,70 +203,66 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginXL
|
||||
}
|
||||
|
||||
// NDivider {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.topMargin: Style.marginXL
|
||||
// Layout.bottomMargin: Style.marginXL
|
||||
// }
|
||||
// Widgets Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
// // Widgets Management Section
|
||||
// ColumnLayout {
|
||||
// spacing: Style.marginXXS
|
||||
// Layout.fillWidth: true
|
||||
NHeader {
|
||||
label: I18n.tr("settings.control-center.shortcuts.section.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.section.description")
|
||||
}
|
||||
|
||||
// NHeader {
|
||||
// label: I18n.tr("settings.control-center.quickSettings.section.label")
|
||||
// description: I18n.tr("settings.control-center.quickSettings.section.description")
|
||||
// }
|
||||
// Sections
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// // Sections
|
||||
// ColumnLayout {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
// Layout.topMargin: Style.marginM
|
||||
// spacing: Style.marginM
|
||||
// Left
|
||||
SectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: ""
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["left"]
|
||||
availableWidgets: availableWidgets
|
||||
enableMoveBetweenSections: false
|
||||
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onDragPotentialStarted: root.handleDragStart()
|
||||
onDragPotentialEnded: root.handleDragEnd()
|
||||
}
|
||||
|
||||
// // Left
|
||||
// SectionEditor {
|
||||
// sectionName: I18n.tr("settings.control-center.quickSettings.sectionLeft")
|
||||
// sectionId: "left"
|
||||
// settingsDialogComponent: ""
|
||||
// widgetRegistry: ControlCenterWidgetRegistry
|
||||
// widgetModel: Settings.data.controlCenter.widgets["left"]
|
||||
// availableWidgets: availableWidgets
|
||||
// enableMoveBetweenSections: false
|
||||
// onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
// onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
// onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
// onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
// onDragPotentialStarted: root.handleDragStart()
|
||||
// onDragPotentialEnded: root.handleDragEnd()
|
||||
// }
|
||||
// Right
|
||||
SectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: ""
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["right"]
|
||||
availableWidgets: availableWidgets
|
||||
enableMoveBetweenSections: false
|
||||
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onDragPotentialStarted: root.handleDragStart()
|
||||
onDragPotentialEnded: root.handleDragEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Right
|
||||
// SectionEditor {
|
||||
// sectionName: I18n.tr("settings.control-center.quickSettings.sectionRight")
|
||||
// sectionId: "right"
|
||||
// settingsDialogComponent: ""
|
||||
// widgetRegistry: ControlCenterWidgetRegistry
|
||||
// widgetModel: Settings.data.controlCenter.widgets["right"]
|
||||
// availableWidgets: availableWidgets
|
||||
// enableMoveBetweenSections: false
|
||||
// onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
// onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
// onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
// onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
// onDragPotentialStarted: root.handleDragStart()
|
||||
// onDragPotentialEnded: root.handleDragEnd()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// NDivider {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.topMargin: Style.marginXL
|
||||
// Layout.bottomMargin: Style.marginXL
|
||||
// }
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL
|
||||
Layout.bottomMargin: Style.marginXL
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Signal functions
|
||||
@@ -165,55 +281,37 @@ ColumnLayout {
|
||||
})
|
||||
}
|
||||
}
|
||||
Settings.data.controlCenter.widgets[section].push(newWidget)
|
||||
Settings.data.controlCenter.shortcuts[section].push(newWidget)
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
if (index >= 0 && index < Settings.data.controlCenter.widgets[section].length) {
|
||||
var newArray = Settings.data.controlCenter.widgets[section].slice()
|
||||
if (index >= 0 && index < Settings.data.controlCenter.shortcuts[section].length) {
|
||||
var newArray = Settings.data.controlCenter.shortcuts[section].slice()
|
||||
var removedWidgets = newArray.splice(index, 1)
|
||||
Settings.data.controlCenter.widgets[section] = newArray
|
||||
|
||||
// Check that we still have a control center
|
||||
if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) {
|
||||
ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000)
|
||||
}
|
||||
Settings.data.controlCenter.shortcuts[section] = newArray
|
||||
}
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.widgets[section].length) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.shortcuts[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.shortcuts[section].length) {
|
||||
|
||||
// Create a new array to avoid modifying the original
|
||||
var newArray = Settings.data.controlCenter.widgets[section].slice()
|
||||
var newArray = Settings.data.controlCenter.shortcuts[section].slice()
|
||||
var item = newArray[fromIndex]
|
||||
newArray.splice(fromIndex, 1)
|
||||
newArray.splice(toIndex, 0, item)
|
||||
|
||||
Settings.data.controlCenter.widgets[section] = newArray
|
||||
//Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray))
|
||||
Settings.data.controlCenter.shortcuts[section] = newArray
|
||||
}
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
// Update the widget settings in the Settings data
|
||||
Settings.data.controlCenter.widgets[section][index] = settings
|
||||
//Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`)
|
||||
Settings.data.controlCenter.shortcuts[section][index] = settings
|
||||
}
|
||||
|
||||
// Base list model for all combo boxes
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Fill out availableWidgets ListModel
|
||||
availableWidgets.clear()
|
||||
ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => {
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": entry
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
// Size properties (matching NIconButton)
|
||||
property real baseSize: Style.baseWidgetSize
|
||||
property bool applyUiScale: true
|
||||
|
||||
// Public properties
|
||||
property string icon: ""
|
||||
property string tooltipText: ""
|
||||
property string tooltipDirection: "auto"
|
||||
property bool enabled: true
|
||||
property bool allowClickWhenDisabled: false
|
||||
property bool compact: false
|
||||
|
||||
// Hot state (unique to NIconButtonHot)
|
||||
property bool hot: false
|
||||
|
||||
// Internal properties
|
||||
property bool hovering: false
|
||||
property bool pressed: false
|
||||
|
||||
// Color properties (matching NIconButton structure)
|
||||
property color colorBg: Color.mSurfaceVariant
|
||||
property color colorFg: Color.mPrimary
|
||||
property color colorBgHover: Color.mTertiary
|
||||
property color colorFgHover: Color.mOnTertiary
|
||||
property color colorBorder: Color.mOutline
|
||||
property color colorBorderHover: Color.mOutline
|
||||
|
||||
// Hot state colors
|
||||
property color colorBgHot: Color.mPrimary
|
||||
property color colorFgHot: Color.mOnPrimary
|
||||
|
||||
// Signals
|
||||
signal entered
|
||||
signal exited
|
||||
signal clicked
|
||||
signal rightClicked
|
||||
signal middleClicked
|
||||
|
||||
// Dimensions (matching NIconButton)
|
||||
implicitWidth: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
|
||||
implicitHeight: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
|
||||
|
||||
// Appearance (matching NIconButton)
|
||||
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
|
||||
color: {
|
||||
if (pressed) {
|
||||
return colorBgHover
|
||||
}
|
||||
if (hot) {
|
||||
return colorBgHot
|
||||
}
|
||||
if (root.enabled && root.hovering) {
|
||||
return colorBgHover
|
||||
}
|
||||
return colorBg
|
||||
}
|
||||
radius: width * 0.5 // Circular like NIconButton
|
||||
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Icon (matching NIconButton positioning and sizing)
|
||||
NIcon {
|
||||
icon: root.icon
|
||||
pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48)
|
||||
applyUiScale: root.applyUiScale
|
||||
color: {
|
||||
if (pressed) {
|
||||
return colorFgHover
|
||||
}
|
||||
if (hot) {
|
||||
return colorFgHot
|
||||
}
|
||||
if (root.enabled && root.hovering) {
|
||||
return colorFgHover
|
||||
}
|
||||
return colorFg
|
||||
}
|
||||
// Center horizontally
|
||||
x: (root.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
y: (root.height - height) / 2 + (height - contentHeight) / 2
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Always enabled to allow hover/tooltip even when the button is disabled
|
||||
enabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
hovering = root.enabled ? true : false
|
||||
if (tooltipText) {
|
||||
TooltipService.show(Screen, parent, tooltipText, tooltipDirection)
|
||||
}
|
||||
root.entered()
|
||||
}
|
||||
|
||||
onExited: {
|
||||
hovering = false
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
root.exited()
|
||||
}
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (root.enabled) {
|
||||
root.pressed = true
|
||||
root.scale = 0.92
|
||||
}
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function (mouse) {
|
||||
root.scale = 1.0
|
||||
root.pressed = false
|
||||
|
||||
if (!root.enabled && !allowClickWhenDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
// Only trigger actions if released while hovering
|
||||
if (root.hovering) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
root.hovering = false
|
||||
root.pressed = false
|
||||
root.scale = 1.0
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property string icon: ""
|
||||
property string tooltipText: ""
|
||||
property bool enabled: true
|
||||
property bool hot: false
|
||||
|
||||
// Styling properties
|
||||
property real iconSize: Style.fontSizeM
|
||||
property real cornerRadius: Style.radiusS
|
||||
|
||||
// Internal properties
|
||||
property bool hovered: false
|
||||
property bool pressed: false
|
||||
|
||||
// Colors
|
||||
property color backgroundColor: {
|
||||
if (pressed) {
|
||||
return Color.mTertiary
|
||||
}
|
||||
if (hot) {
|
||||
return Color.mPrimary
|
||||
}
|
||||
return Color.mSurface
|
||||
}
|
||||
property color iconColor: {
|
||||
if (pressed) {
|
||||
return Color.mOnTertiary
|
||||
}
|
||||
if (hot) {
|
||||
return Color.mOnPrimary
|
||||
}
|
||||
return Color.mOnSurface
|
||||
}
|
||||
property color hoverColor: Color.mTertiary
|
||||
property color hoverIconColor: Color.mOnTertiary
|
||||
|
||||
// Signals
|
||||
signal clicked
|
||||
signal rightClicked
|
||||
signal middleClicked
|
||||
|
||||
// Dimensions
|
||||
implicitWidth: Style.baseWidgetSize * 0.8
|
||||
implicitHeight: Style.baseWidgetSize * 0.8
|
||||
|
||||
// Appearance
|
||||
radius: cornerRadius
|
||||
color: {
|
||||
if (!enabled)
|
||||
return Qt.lighter(Color.mSurface, 1.1)
|
||||
if (hovered)
|
||||
return hoverColor
|
||||
return backgroundColor
|
||||
}
|
||||
|
||||
border.width: 0
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: root.icon !== ""
|
||||
icon: root.icon
|
||||
pointSize: root.iconSize
|
||||
color: {
|
||||
if (!root.enabled)
|
||||
return Color.mOnSurfaceVariant
|
||||
if (root.hovered)
|
||||
return root.hoverIconColor
|
||||
return root.iconColor
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
enabled: root.enabled
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onEntered: {
|
||||
root.hovered = true
|
||||
if (tooltipText) {
|
||||
TooltipService.show(Screen, root, root.tooltipText)
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
root.hovered = false
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: mouse => {
|
||||
root.pressed = true
|
||||
root.scale = 0.92
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
root.scale = 1.0
|
||||
root.pressed = false
|
||||
|
||||
// Only trigger actions if released while hovering
|
||||
if (root.hovered) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
root.hovered = false
|
||||
root.pressed = false
|
||||
root.scale = 1.0
|
||||
if (tooltipText) {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Public API
|
||||
property var model: []
|
||||
property color activeColor: Color.mPrimary
|
||||
property color activeOnColor: Color.mOnPrimary
|
||||
property color dragHandleColor: Color.mOutline
|
||||
property int baseSize: Style.baseWidgetSize * 0.7
|
||||
property int spacing: Style.marginM
|
||||
|
||||
signal itemToggled(int index, bool enabled)
|
||||
signal itemsReordered(int fromIndex, int toIndex)
|
||||
signal dragPotentialStarted
|
||||
signal dragPotentialEnded
|
||||
|
||||
implicitHeight: listView.contentHeight
|
||||
|
||||
function toggleItem(index) {
|
||||
if (index < 0 || index >= root.model.length)
|
||||
return
|
||||
|
||||
var item = root.model[index]
|
||||
if (item.required)
|
||||
return
|
||||
|
||||
// Create a new array to trigger binding update
|
||||
var newModel = root.model.slice()
|
||||
newModel[index] = Object.assign({}, item, {
|
||||
"enabled": !item.enabled
|
||||
})
|
||||
root.model = newModel
|
||||
|
||||
root.itemToggled(index, newModel[index].enabled)
|
||||
}
|
||||
|
||||
function moveItem(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex)
|
||||
return
|
||||
if (fromIndex < 0 || fromIndex >= root.model.length)
|
||||
return
|
||||
if (toIndex < 0 || toIndex >= root.model.length)
|
||||
return
|
||||
|
||||
// Create a new array with item moved
|
||||
var newModel = root.model.slice()
|
||||
var item = newModel.splice(fromIndex, 1)[0]
|
||||
newModel.splice(toIndex, 0, item)
|
||||
root.model = newModel
|
||||
|
||||
root.itemsReordered(fromIndex, toIndex)
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: root.spacing
|
||||
interactive: false
|
||||
clip: true
|
||||
model: root.model
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
width: listView.width
|
||||
height: checkboxRow.height
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
property string text: modelData.text || ""
|
||||
property bool enabled: modelData.enabled || false
|
||||
property bool required: modelData.required || false
|
||||
property bool dragging: false
|
||||
property int dragStartY: 0
|
||||
property int dragStartIndex: -1
|
||||
property int dragTargetIndex: -1
|
||||
property int itemSpacing: root.spacing
|
||||
|
||||
RowLayout {
|
||||
id: checkboxRow
|
||||
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
|
||||
Layout.preferredWidth: root.baseSize
|
||||
Layout.preferredHeight: root.baseSize
|
||||
radius: Style.radiusXS
|
||||
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : Color.transparent
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
Rectangle {
|
||||
Layout.preferredWidth: root.baseSize * 0.4
|
||||
Layout.preferredHeight: 2
|
||||
radius: 1
|
||||
color: root.dragHandleColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragHandleMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
hoverEnabled: true
|
||||
preventStealing: false
|
||||
z: 1000
|
||||
|
||||
onPressed: mouse => {
|
||||
delegateItem.dragStartIndex = delegateItem.index
|
||||
delegateItem.dragTargetIndex = delegateItem.index
|
||||
delegateItem.dragStartY = delegateItem.y
|
||||
delegateItem.dragging = true
|
||||
delegateItem.z = 999
|
||||
|
||||
// Signal that interaction started (prevents panel close)
|
||||
preventStealing = true
|
||||
root.dragPotentialStarted()
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (delegateItem.dragging) {
|
||||
var dy = mouse.y - dragHandle.height / 2
|
||||
var newY = delegateItem.y + dy
|
||||
|
||||
// Constrain within bounds
|
||||
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height))
|
||||
delegateItem.y = newY
|
||||
|
||||
// Calculate target index (but don't apply yet)
|
||||
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + delegateItem.itemSpacing))
|
||||
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1))
|
||||
|
||||
delegateItem.dragTargetIndex = targetIndex
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
// Always signal end of interaction
|
||||
preventStealing = false
|
||||
root.dragPotentialEnded()
|
||||
|
||||
// Apply the model change now that drag is complete
|
||||
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
|
||||
root.moveItem(delegateItem.dragStartIndex, delegateItem.dragTargetIndex)
|
||||
}
|
||||
|
||||
delegateItem.dragging = false
|
||||
delegateItem.dragStartIndex = -1
|
||||
delegateItem.dragTargetIndex = -1
|
||||
delegateItem.z = 0
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
// Handle cancel (e.g., ESC key pressed during drag)
|
||||
preventStealing = false
|
||||
root.dragPotentialEnded()
|
||||
|
||||
delegateItem.dragging = false
|
||||
delegateItem.dragStartIndex = -1
|
||||
delegateItem.dragTargetIndex = -1
|
||||
delegateItem.z = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
Rectangle {
|
||||
id: box
|
||||
|
||||
Layout.preferredWidth: root.baseSize
|
||||
Layout.preferredHeight: root.baseSize
|
||||
radius: Style.radiusXS
|
||||
color: delegateItem.enabled ? root.activeColor : Color.mSurface
|
||||
border.color: delegateItem.required ? root.activeColor : Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
opacity: delegateItem.required ? 0.7 : 1.0
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: delegateItem.enabled
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
icon: "check"
|
||||
color: root.activeOnColor
|
||||
pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5)
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: delegateItem.required ? Qt.ForbiddenCursor : Qt.PointingHandCursor
|
||||
enabled: !delegateItem.required
|
||||
|
||||
onClicked: {
|
||||
root.toggleItem(delegateItem.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Label
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: delegateItem.text
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Required indicator
|
||||
NText {
|
||||
visible: delegateItem.required
|
||||
text: "(required)"
|
||||
color: Color.mOnSurfaceVariant
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Position binding for non-dragging state
|
||||
y: {
|
||||
if (delegateItem.dragging) {
|
||||
return delegateItem.y
|
||||
}
|
||||
|
||||
// Check if any item is being dragged
|
||||
var draggedIndex = -1
|
||||
var targetIndex = -1
|
||||
for (var i = 0; i < listView.count; i++) {
|
||||
var item = listView.itemAtIndex(i)
|
||||
if (item && item.dragging) {
|
||||
draggedIndex = item.dragStartIndex
|
||||
targetIndex = item.dragTargetIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If an item is being dragged, adjust positions
|
||||
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
||||
var currentIndex = delegateItem.index
|
||||
|
||||
if (draggedIndex < targetIndex) {
|
||||
// Dragging down: shift items up between draggedIndex and targetIndex
|
||||
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
|
||||
return (currentIndex - 1) * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
} else {
|
||||
// Dragging up: shift items down between targetIndex and draggedIndex
|
||||
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
|
||||
return (currentIndex + 1) * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delegateItem.index * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !delegateItem.dragging
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user