Post refactoring fixes 1/??
This commit is contained in:
parent
605e3610d2
commit
4631239b92
19 changed files with 8 additions and 11 deletions
369
Modules/SettingsPanel/SettingsPanel.qml
Normal file
369
Modules/SettingsPanel/SettingsPanel.qml
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Modules.SettingsPanel.Tabs as Tabs
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NLoader {
|
||||
id: root
|
||||
|
||||
// Tabs enumeration, order is NOT relevant
|
||||
enum Tab {
|
||||
About,
|
||||
AudioService,
|
||||
Bar,
|
||||
ColorScheme,
|
||||
Display,
|
||||
General,
|
||||
Network,
|
||||
ScreenRecorder,
|
||||
TimeWeather,
|
||||
Wallpaper,
|
||||
WallpaperSelector
|
||||
}
|
||||
|
||||
property int requestedTab: SettingsPanel.Tab.General
|
||||
|
||||
content: Component {
|
||||
NPanel {
|
||||
id: panel
|
||||
|
||||
property int currentTabIndex: 0
|
||||
|
||||
// Override hide function to animate first
|
||||
function hide() {
|
||||
// Start hide animation
|
||||
bgRect.scaleValue = 0.8
|
||||
bgRect.opacityValue = 0.0
|
||||
// Hide after animation completes
|
||||
hideTimer.start()
|
||||
}
|
||||
|
||||
// Connect to NPanel's dismissed signal to handle external close events
|
||||
Connections {
|
||||
target: panel
|
||||
function onDismissed() {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to hide panel after animation
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: Style.animationSlow
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
panel.visible = false
|
||||
panel.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
Component {
|
||||
id: generalTab
|
||||
Tabs.GeneralTab {}
|
||||
}
|
||||
Component {
|
||||
id: barTab
|
||||
Tabs.BarTab {}
|
||||
}
|
||||
Component {
|
||||
id: audioTab
|
||||
Tabs.AudioTab {}
|
||||
}
|
||||
Component {
|
||||
id: displayTab
|
||||
Tabs.DisplayTab {}
|
||||
}
|
||||
Component {
|
||||
id: networkTab
|
||||
Tabs.NetworkTab {}
|
||||
}
|
||||
Component {
|
||||
id: timeWeatherTab
|
||||
Tabs.TimeWeatherTab {}
|
||||
}
|
||||
Component {
|
||||
id: colorSchemeTab
|
||||
Tabs.ColorSchemeTab {}
|
||||
}
|
||||
Component {
|
||||
id: wallpaperTab
|
||||
Tabs.WallpaperTab {}
|
||||
}
|
||||
Component {
|
||||
id: wallpaperSelectorTab
|
||||
Tabs.WallpaperSelectorTab {}
|
||||
}
|
||||
Component {
|
||||
id: screenRecorderTab
|
||||
Tabs.ScreenRecorderTab {}
|
||||
}
|
||||
Component {
|
||||
id: aboutTab
|
||||
Tabs.AboutTab {}
|
||||
}
|
||||
|
||||
property var tabsModel: [{
|
||||
"id": SettingsPanel.Tab.General,
|
||||
"label": "General",
|
||||
"icon": "tune",
|
||||
"source": generalTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Bar,
|
||||
"label": "Bar",
|
||||
"icon": "web_asset",
|
||||
"source": barTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.AudioService,
|
||||
"label": "AudioService",
|
||||
"icon": "volume_up",
|
||||
"source": audioTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Display,
|
||||
"label": "Display",
|
||||
"icon": "monitor",
|
||||
"source": displayTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Network,
|
||||
"label": "Network",
|
||||
"icon": "lan",
|
||||
"source": networkTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.TimeWeather,
|
||||
"label": "Time & Weather",
|
||||
"icon": "schedule",
|
||||
"source": timeWeatherTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ColorScheme,
|
||||
"label": "Color Scheme",
|
||||
"icon": "palette",
|
||||
"source": colorSchemeTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Wallpaper,
|
||||
"label": "Wallpaper",
|
||||
"icon": "image",
|
||||
"source": wallpaperTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.WallpaperSelector,
|
||||
"label": "Wallpaper Selector",
|
||||
"icon": "wallpaper_slideshow",
|
||||
"source": wallpaperSelectorTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "videocam",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "info",
|
||||
"source": aboutTab
|
||||
}]
|
||||
|
||||
Component.onCompleted: {
|
||||
var initialIndex = 0
|
||||
if (root.requestedTab !== null) {
|
||||
for (var i = 0; i < panel.tabsModel.length; i++) {
|
||||
if (panel.tabsModel[i].id === root.requestedTab) {
|
||||
initialIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now that the UI is settled, set the current tab index.
|
||||
panel.currentTabIndex = initialIndex
|
||||
show()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && (bgRect.opacityValue > 0)) {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
color: Colors.mSurface
|
||||
radius: Style.radiusLarge * scaling
|
||||
border.color: Colors.mOutlineVariant
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
layer.enabled: true
|
||||
width: Math.max(screen.width * 0.5, 1280) * scaling
|
||||
height: Math.max(screen.height * 0.5, 720) * scaling
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Animation properties
|
||||
property real scaleValue: 0.8
|
||||
property real opacityValue: 0.0
|
||||
|
||||
scale: scaleValue
|
||||
opacity: opacityValue
|
||||
|
||||
// Animate in when component is completed
|
||||
Component.onCompleted: {
|
||||
scaleValue = 1.0
|
||||
opacityValue = 1.0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginLarge * scaling
|
||||
spacing: Style.marginLarge * scaling
|
||||
|
||||
Rectangle {
|
||||
id: sidebar
|
||||
Layout.preferredWidth: Style.sliderWidth * 1.3 * scaling
|
||||
Layout.fillHeight: true
|
||||
color: Colors.mSurfaceVariant
|
||||
border.color: Colors.mOutlineVariant
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
radius: Style.radiusMedium * scaling
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginSmall * scaling
|
||||
spacing: Style.marginTiny * 1.5 * scaling
|
||||
|
||||
Repeater {
|
||||
id: sections
|
||||
model: panel.tabsModel
|
||||
delegate: Rectangle {
|
||||
id: tabItem
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
radius: Style.radiusSmall * scaling
|
||||
color: selected ? Colors.mPrimary : (tabItem.hovering ? Colors.mTertiary : "transparent")
|
||||
readonly property bool selected: index === currentTabIndex
|
||||
property bool hovering: false
|
||||
property color tabTextColor: selected ? Colors.mOnPrimary : (tabItem.hovering ? Colors.mOnTertiary : Colors.mOnSurface)
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginSmall * scaling
|
||||
anchors.rightMargin: Style.marginSmall * scaling
|
||||
spacing: Style.marginSmall * scaling
|
||||
// Tab icon on the left side
|
||||
NText {
|
||||
text: modelData.icon
|
||||
color: tabTextColor
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.variableAxes: {
|
||||
"wght": (Font.Normal + Font.Bold) / 2.0
|
||||
}
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
}
|
||||
// Tab label on the left side
|
||||
NText {
|
||||
text: modelData.label
|
||||
color: tabTextColor
|
||||
font.pointSize: Style.fontSizeMedium * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: tabItem.hovering = true
|
||||
onExited: tabItem.hovering = false
|
||||
onCanceled: tabItem.hovering = false
|
||||
onClicked: currentTabIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Rectangle {
|
||||
id: contentPane
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Style.radiusMedium * scaling
|
||||
color: Colors.mSurfaceVariant
|
||||
border.color: Colors.mOutlineVariant
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginLarge * scaling
|
||||
spacing: Style.marginSmall * scaling
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginSmall * scaling
|
||||
|
||||
// Tab label on the main right side
|
||||
NText {
|
||||
text: panel.tabsModel[currentTabIndex].label
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: panel.hide()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
Repeater {
|
||||
model: panel.tabsModel
|
||||
|
||||
onItemAdded: function (index, item) {
|
||||
item.sourceComponent = panel.tabsModel[index].source
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
// All loaders will occupy the same space, stacked on top of each other.
|
||||
anchors.fill: parent
|
||||
visible: index === panel.currentTabIndex
|
||||
// The loader is only active (and uses memory) when its page is visible.
|
||||
active: visible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
264
Modules/SettingsPanel/Tabs/AboutTab.qml
Normal file
264
Modules/SettingsPanel/Tabs/AboutTab.qml
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: "Unknown" // Fallback version
|
||||
property var contributors: GitHubService.contributors
|
||||
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Process {
|
||||
id: currentVersionProcess
|
||||
|
||||
command: ["sh", "-c", "cd " + Quickshell.shellDir + " && git describe --tags --abbrev=0 2>/dev/null || echo 'Unknown'"]
|
||||
Component.onCompleted: {
|
||||
running = true
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const version = text.trim()
|
||||
if (version && version !== "Unknown") {
|
||||
root.currentVersion = version
|
||||
} else {
|
||||
currentVersionProcess.command = ["sh", "-c", "cd " + Quickshell.shellDir
|
||||
+ " && cat package.json 2>/dev/null | grep '\"version\"' | cut -d'\"' -f4 || echo 'Unknown'"]
|
||||
currentVersionProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginLarge * scaling
|
||||
rightPadding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
text: "Noctalia: quiet by design"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "It may just be another quickshell setup but it won't get in your way."
|
||||
font.pointSize: Style.fontSizeMedium * scaling
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
columns: 2
|
||||
rowSpacing: Style.marginTiny * scaling
|
||||
columnSpacing: Style.marginSmall * scaling
|
||||
|
||||
NText {
|
||||
text: "Latest Version:"
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.latestVersion
|
||||
color: Colors.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Installed Version:"
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Colors.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
Layout.preferredWidth: updateText.implicitWidth + 46 * scaling
|
||||
Layout.preferredHeight: Style.barHeight * scaling
|
||||
radius: Style.radiusLarge * scaling
|
||||
color: updateArea.containsMouse ? Colors.mPrimary : "transparent"
|
||||
border.color: Colors.mPrimary
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
visible: {
|
||||
if (root.currentVersion === "Unknown" || root.latestVersion === "Unknown")
|
||||
return false
|
||||
|
||||
const latest = root.latestVersion.replace("v", "").split(".")
|
||||
const current = root.currentVersion.replace("v", "").split(".")
|
||||
for (var i = 0; i < Math.max(latest.length, current.length); i++) {
|
||||
const l = parseInt(latest[i] || "0")
|
||||
const c = parseInt(current[i] || "0")
|
||||
if (l > c)
|
||||
return true
|
||||
|
||||
if (l < c)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginSmall * scaling
|
||||
|
||||
NText {
|
||||
text: "system_update"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
color: updateArea.containsMouse ? Colors.mSurface : Colors.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
id: updateText
|
||||
text: "Download latest release"
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
color: updateArea.containsMouse ? Colors.mSurface : Colors.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updateArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `Shout-out to our ${root.contributors.length} awesome contributors!`
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: Style.marginLarge * 2
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: 200 * Style.marginTiny * scaling
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Style.marginLarge * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
GridView {
|
||||
id: contributorsGrid
|
||||
|
||||
anchors.fill: parent
|
||||
width: 200 * 4 * scaling
|
||||
height: Math.ceil(root.contributors.length / 4) * 100
|
||||
cellWidth: Style.baseWidgetSize * 6.25 * scaling
|
||||
cellHeight: Style.baseWidgetSize * 3.125 * scaling
|
||||
model: root.contributors
|
||||
|
||||
delegate: Rectangle {
|
||||
width: contributorsGrid.cellWidth - Style.marginLarge * scaling
|
||||
height: contributorsGrid.cellHeight - Style.marginTiny * scaling
|
||||
radius: Style.radiusLarge * scaling
|
||||
color: contributorArea.containsMouse ? Colors.mTertiary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginSmall * scaling
|
||||
spacing: Style.marginMedium * scaling
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling
|
||||
|
||||
NImageRounded {
|
||||
imagePath: modelData.avatar_url || ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginTiny * scaling
|
||||
fallbackIcon: "person"
|
||||
borderColor: Colors.mPrimary
|
||||
borderWidth: Math.max(1, Style.borderMedium * scaling)
|
||||
imageRadius: width * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiny * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Colors.mSurface : Colors.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: (modelData.contributions || 0) + " " + ((modelData.contributions
|
||||
|| 0) === 1 ? "commit" : "commits")
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: contributorArea.containsMouse ? Colors.mSurface : Colors.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.html_url)
|
||||
Quickshell.execDetached(["xdg-open", modelData.html_url])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
276
Modules/SettingsPanel/Tabs/AudioTab.qml
Normal file
276
Modules/SettingsPanel/Tabs/AudioTab.qml
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
// Connection used to open the pill when volume changes
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume
|
||||
}
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiny * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "AudioService"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Volume Controls
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
// Master Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiniest * scaling
|
||||
|
||||
NText {
|
||||
text: "Master Volume"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "System-wide volume level"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
|
||||
// Probably because they have some quick fades in and out to avoid clipping
|
||||
// We use a timer to space out the updates, to avoid lock up
|
||||
Timer {
|
||||
interval: Style.animationFast
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
onMoved: {
|
||||
localVolume = value
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginMedium * scaling
|
||||
|
||||
NToggle {
|
||||
label: "Mute AudioService"
|
||||
description: "Mute or unmute the default audio output"
|
||||
value: AudioService.muted
|
||||
onToggled: function (newValue) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// AudioService Devices
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "AudioService Devices"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiniest * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
|
||||
NText {
|
||||
text: "Output Device"
|
||||
font.pointSize: Style.fontSizeMedium * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Select the desired audio output device"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sinks
|
||||
NRadioButton {
|
||||
required property PwNode modelData
|
||||
ButtonGroup.group: sinks
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSink(modelData)
|
||||
text: modelData.description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Input Devices
|
||||
ButtonGroup {
|
||||
id: sources
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiniest * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
|
||||
NText {
|
||||
text: "Input Device"
|
||||
font.pointSize: Style.fontSizeMedium * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Select desired audio input device"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sources
|
||||
NRadioButton {
|
||||
required property PwNode modelData
|
||||
ButtonGroup.group: sources
|
||||
checked: AudioService.source?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSource(modelData)
|
||||
text: modelData.description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * scaling
|
||||
Layout.bottomMargin: Style.marginMedium * scaling
|
||||
}
|
||||
|
||||
// AudioService Visualizer Category
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "AudioService Visualizer"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// AudioService Visualizer section
|
||||
NComboBox {
|
||||
id: audioVisualizerCombo
|
||||
label: "Visualization Type"
|
||||
description: "Choose a visualization type for media playback"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "none"
|
||||
name: "None"
|
||||
}
|
||||
ListElement {
|
||||
key: "linear"
|
||||
name: "Linear"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
onSelected: function (key) {
|
||||
Settings.data.audio.visualizerType = key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Modules/SettingsPanel/Tabs/BarTab.qml
Normal file
81
Modules/SettingsPanel/Tabs/BarTab.qml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Components"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Active Window"
|
||||
description: "Display the title of the currently focused window below the bar"
|
||||
value: Settings.data.bar.showActiveWindow
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.bar.showActiveWindow = newValue
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show System Info"
|
||||
description: "Display system information (CPU, RAM, Temperature)"
|
||||
value: Settings.data.bar.showSystemInfo
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.bar.showSystemInfo = newValue
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Taskbar"
|
||||
description: "Display a taskbar showing currently open windows"
|
||||
value: Settings.data.bar.showTaskbar
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.bar.showTaskbar = newValue
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Media"
|
||||
description: "Display media controls and information"
|
||||
value: Settings.data.bar.showMedia
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.bar.showMedia = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
325
Modules/SettingsPanel/Tabs/ColorSchemeTab.qml
Normal file
325
Modules/SettingsPanel/Tabs/ColorSchemeTab.qml
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import Quickshell.Io
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
// Helper function to get color from scheme file
|
||||
function getSchemeColor(schemePath, colorKey) {
|
||||
// Extract scheme name from path
|
||||
var schemeName = schemePath.split("/").pop().replace(".json", "")
|
||||
|
||||
// Try to get from cached data first
|
||||
if (schemeColorsCache[schemeName] && schemeColorsCache[schemeName][colorKey]) {
|
||||
return schemeColorsCache[schemeName][colorKey]
|
||||
}
|
||||
|
||||
// Return a default color if not cached yet
|
||||
return "#000000"
|
||||
}
|
||||
|
||||
// Cache for scheme colors
|
||||
property var schemeColorsCache: ({})
|
||||
|
||||
// Scale properties for card animations
|
||||
property real cardScaleLow: 0.95
|
||||
property real cardScaleHigh: 1.0
|
||||
|
||||
// This function is called by the FileView Repeater when a scheme file is loaded
|
||||
function schemeLoaded(schemeName, jsonData) {
|
||||
var colors = {}
|
||||
|
||||
// Extract colors from JSON data
|
||||
if (jsonData && typeof jsonData === 'object') {
|
||||
colors.mPrimary = jsonData.mPrimary || jsonData.primary || "#000000"
|
||||
colors.mSecondary = jsonData.mSecondary || jsonData.secondary || "#000000"
|
||||
colors.mTertiary = jsonData.mTertiary || jsonData.tertiary || "#000000"
|
||||
colors.mError = jsonData.mError || jsonData.error || "#ff0000"
|
||||
colors.mSurface = jsonData.mSurface || jsonData.surface || "#ffffff"
|
||||
colors.mOnSurface = jsonData.mOnSurface || jsonData.onSurface || "#000000"
|
||||
colors.mOutline = jsonData.mOutline || jsonData.outline || "#666666"
|
||||
} else {
|
||||
// Default colors on failure
|
||||
colors = {
|
||||
"mPrimary": "#000000",
|
||||
"mSecondary": "#000000",
|
||||
"mTertiary": "#000000",
|
||||
"mError": "#ff0000",
|
||||
"mSurface": "#ffffff",
|
||||
"mOnSurface": "#000000",
|
||||
"mOutline": "#666666"
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cache. This must be done by re-assigning the whole object to trigger updates.
|
||||
var newCache = schemeColorsCache
|
||||
newCache[schemeName] = colors
|
||||
schemeColorsCache = newCache
|
||||
}
|
||||
|
||||
// When the list of available schemes changes, clear the cache.
|
||||
// The Repeater below will automatically re-create the FileViews.
|
||||
Connections {
|
||||
target: ColorSchemesService
|
||||
function onSchemesChanged() {
|
||||
schemeColorsCache = {}
|
||||
}
|
||||
}
|
||||
|
||||
// A non-visual Item to host the Repeater that loads the color scheme files.
|
||||
Item {
|
||||
visible: false
|
||||
id: fileLoaders
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemesService.schemes
|
||||
|
||||
// The delegate is a Component, which correctly wraps the non-visual FileView
|
||||
delegate: Item {
|
||||
FileView {
|
||||
path: modelData
|
||||
blockLoading: true
|
||||
onLoaded: {
|
||||
var schemeName = path.split("/").pop().replace(".json", "")
|
||||
try {
|
||||
var jsonData = JSON.parse(text())
|
||||
root.schemeLoaded(schemeName, jsonData)
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse JSON for scheme:", schemeName, e)
|
||||
root.schemeLoaded(schemeName, null) // Load defaults on parse error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI Code
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Use Matugen
|
||||
NToggle {
|
||||
label: "Use Matugen"
|
||||
description: "Automatically generate colors based on your active wallpaper using Matugen"
|
||||
value: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.colorSchemes.useWallpaperColors = newValue
|
||||
if (Settings.data.colorSchemes.useWallpaperColors) {
|
||||
ColorSchemesService.changedWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiniest * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead."
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiny * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * scaling
|
||||
|
||||
// Color Schemes Grid
|
||||
GridLayout {
|
||||
columns: 4
|
||||
rowSpacing: Style.marginLarge * scaling
|
||||
columnSpacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemesService.schemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeCard
|
||||
|
||||
property string schemePath: modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 120 * scaling
|
||||
radius: Style.radiusMedium * scaling
|
||||
color: getSchemeColor(modelData, "mSurface")
|
||||
border.width: Math.max(1, Style.borderThick * scaling)
|
||||
border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Colors.mPrimary : Colors.mOutline
|
||||
scale: root.cardScaleLow
|
||||
|
||||
// Mouse area for selection
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Disable useWallpaperColors when picking a predefined color scheme
|
||||
// TBC: broken uncheck useWallpaperColors
|
||||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
Settings.data.colorSchemes.predefinedScheme = schemePath
|
||||
ColorSchemesService.applyScheme(schemePath)
|
||||
}
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onEntered: {
|
||||
schemeCard.scale = root.cardScaleHigh
|
||||
}
|
||||
|
||||
onExited: {
|
||||
schemeCard.scale = root.cardScaleLow
|
||||
}
|
||||
}
|
||||
|
||||
// Card content
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXL * scaling
|
||||
spacing: Style.marginSmall * scaling
|
||||
|
||||
// Scheme name
|
||||
NText {
|
||||
text: {
|
||||
// Remove json and the full path
|
||||
var chunks = schemePath.replace(".json", "").split("/")
|
||||
return chunks[chunks.length - 1]
|
||||
}
|
||||
font.pointSize: Style.fontSizeMedium * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: getSchemeColor(modelData, "mOnSurface")
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
// Color swatches
|
||||
RowLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Primary color swatch
|
||||
Rectangle {
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mPrimary")
|
||||
}
|
||||
|
||||
// Secondary color swatch
|
||||
Rectangle {
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mSecondary")
|
||||
}
|
||||
|
||||
// Tertiary color swatch
|
||||
Rectangle {
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mTertiary")
|
||||
}
|
||||
|
||||
// Error color swatch
|
||||
Rectangle {
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mError")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selection indicator
|
||||
Rectangle {
|
||||
visible: Settings.data.colorSchemes.predefinedScheme === schemePath
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginSmall * scaling
|
||||
width: 24 * scaling
|
||||
height: 24 * scaling
|
||||
radius: width * 0.5
|
||||
color: Colors.mPrimary
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: "✓"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth animations
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
191
Modules/SettingsPanel/Tabs/DisplayTab.qml
Normal file
191
Modules/SettingsPanel/Tabs/DisplayTab.qml
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
property real scaling: 1
|
||||
readonly property string tabIcon: "monitor"
|
||||
readonly property string tabLabel: "Display"
|
||||
readonly property int tabIndex: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice()
|
||||
if (!arr.includes(name))
|
||||
arr.push(name)
|
||||
return arr
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name
|
||||
})
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
|
||||
contentWidth: parent.width
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.margins: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Per‑monitor configuration"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "By default, bars and notifications are shown on all displays. Select one or more below to narrow your view."
|
||||
font.pointSize: Style.fontSize * scaling
|
||||
color: Colors.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: Rectangle {
|
||||
Layout.fillWidth: true
|
||||
radius: Style.radiusMedium * scaling
|
||||
color: Colors.mSurface
|
||||
border.color: Colors.mOutline
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginLarge * scaling
|
||||
spacing: Style.marginTiniest * scaling
|
||||
|
||||
NText {
|
||||
text: (modelData.name || "Unknown")
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mSecondary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})`
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
|
||||
NToggle {
|
||||
label: "Bar"
|
||||
description: "Enable the top bar on this monitor"
|
||||
value: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: function (newValue) {
|
||||
if (newValue) {
|
||||
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Notifications"
|
||||
description: "Enable notifications on this monitor"
|
||||
value: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: function (newValue) {
|
||||
if (newValue) {
|
||||
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors,
|
||||
modelData.name)
|
||||
} else {
|
||||
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors,
|
||||
modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Dock"
|
||||
description: "Enable the dock on this monitor"
|
||||
value: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: function (newValue) {
|
||||
if (newValue) {
|
||||
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// Brightness Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
NText {
|
||||
text: "Brightness Step Size"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the step size for brightness changes (scroll wheel, ipc bind)"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginMedium * scaling
|
||||
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
Settings.data.brightness.brightnessStep = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Settings.data.brightness.brightnessStep + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Modules/SettingsPanel/Tabs/GeneralTab.qml
Normal file
124
Modules/SettingsPanel/Tabs/GeneralTab.qml
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "General Settings"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
// Profile section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginLarge * scaling
|
||||
|
||||
// Avatar preview
|
||||
NImageRounded {
|
||||
width: 64 * scaling
|
||||
height: 64 * scaling
|
||||
imagePath: Settings.data.general.avatarImage
|
||||
fallbackIcon: "person"
|
||||
borderColor: Colors.mPrimary
|
||||
borderWidth: Math.max(1, Style.borderMedium)
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: "Profile Picture"
|
||||
description: "Your profile picture displayed in various places throughout the shell"
|
||||
text: Settings.data.general.avatarImage
|
||||
placeholderText: "/home/user/.face"
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: {
|
||||
Settings.data.general.avatarImage = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "User Interface"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Corners"
|
||||
description: "Display rounded corners on the edge of the screen"
|
||||
value: Settings.data.general.showScreenCorners
|
||||
onToggled: function (v) {
|
||||
Settings.data.general.showScreenCorners = v
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Dim Desktop"
|
||||
description: "Dim the desktop when panels or menus are open"
|
||||
value: Settings.data.general.dimDesktop
|
||||
onToggled: function (v) {
|
||||
Settings.data.general.dimDesktop = v
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Auto-hide Dock"
|
||||
description: "Automatically hide the dock when not in use"
|
||||
value: Settings.data.dock.autoHide
|
||||
onToggled: function (v) {
|
||||
Settings.data.dock.autoHide = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Modules/SettingsPanel/Tabs/NetworkTab.qml
Normal file
65
Modules/SettingsPanel/Tabs/NetworkTab.qml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Interfaces"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "WiFi Enabled"
|
||||
description: "Enable WiFi connectivity"
|
||||
value: Settings.data.network.wifiEnabled
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.network.wifiEnabled = newValue
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Bluetooth Enabled"
|
||||
description: "Enable Bluetooth connectivity"
|
||||
value: Settings.data.network.bluetoothEnabled
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.network.bluetoothEnabled = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
270
Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml
Normal file
270
Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiny * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Recording"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Output Directory
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
NTextInput {
|
||||
label: "Output Directory"
|
||||
description: "Directory where screen recordings will be saved"
|
||||
placeholderText: "/home/xxx/Videos"
|
||||
text: Settings.data.screenRecorder.directory
|
||||
onEditingFinished: {
|
||||
Settings.data.screenRecorder.directory = text
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginMedium * scaling
|
||||
// Show Cursor
|
||||
NToggle {
|
||||
label: "Show Cursor"
|
||||
description: "Record mouse cursor in the video"
|
||||
value: Settings.data.screenRecorder.showCursor
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.screenRecorder.showCursor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// Video Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Video Settings"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Frame Rate
|
||||
NComboBox {
|
||||
label: "Frame Rate"
|
||||
description: "Target frame rate for screen recordings (default: 60)"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "30"
|
||||
name: "30 FPS"
|
||||
}
|
||||
ListElement {
|
||||
key: "60"
|
||||
name: "60 FPS"
|
||||
}
|
||||
ListElement {
|
||||
key: "120"
|
||||
name: "120 FPS"
|
||||
}
|
||||
ListElement {
|
||||
key: "240"
|
||||
name: "240 FPS"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.frameRate
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.frameRate = key
|
||||
}
|
||||
}
|
||||
|
||||
// Video Quality
|
||||
NComboBox {
|
||||
label: "Video Quality"
|
||||
description: "Higher quality results in larger file sizes"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "medium"
|
||||
name: "Medium"
|
||||
}
|
||||
ListElement {
|
||||
key: "high"
|
||||
name: "High"
|
||||
}
|
||||
ListElement {
|
||||
key: "very_high"
|
||||
name: "Very High"
|
||||
}
|
||||
ListElement {
|
||||
key: "ultra"
|
||||
name: "Ultra"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.quality
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.quality = key
|
||||
}
|
||||
}
|
||||
|
||||
// Video Codec
|
||||
NComboBox {
|
||||
label: "Video Codec"
|
||||
description: "Different codecs offer different compression and compatibility"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "h264"
|
||||
name: "H264"
|
||||
}
|
||||
ListElement {
|
||||
key: "hevc"
|
||||
name: "HEVC"
|
||||
}
|
||||
ListElement {
|
||||
key: "av1"
|
||||
name: "AV1"
|
||||
}
|
||||
ListElement {
|
||||
key: "vp8"
|
||||
name: "VP8"
|
||||
}
|
||||
ListElement {
|
||||
key: "vp9"
|
||||
name: "VP9"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.videoCodec
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.videoCodec = key
|
||||
}
|
||||
}
|
||||
|
||||
// Color Range
|
||||
NComboBox {
|
||||
label: "Color Range"
|
||||
description: "Limited is recommended for better compatibility"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "limited"
|
||||
name: "Limited"
|
||||
}
|
||||
ListElement {
|
||||
key: "full"
|
||||
name: "Full"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.colorRange
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.colorRange = key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// AudioService Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "AudioService Settings"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// AudioService Source
|
||||
NComboBox {
|
||||
label: "AudioService Source"
|
||||
description: "AudioService source to capture during recording"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "default_output"
|
||||
name: "System Output"
|
||||
}
|
||||
ListElement {
|
||||
key: "default_input"
|
||||
name: "Microphone Input"
|
||||
}
|
||||
ListElement {
|
||||
key: "both"
|
||||
name: "System Output + Microphone Input"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.audioSource
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.audioSource = key
|
||||
}
|
||||
}
|
||||
|
||||
// AudioService Codec
|
||||
NComboBox {
|
||||
label: "AudioService Codec"
|
||||
description: "Opus is recommended for best performance and smallest audio size"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "opus"
|
||||
name: "Opus"
|
||||
}
|
||||
ListElement {
|
||||
key: "aac"
|
||||
name: "AAC"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.audioCodec
|
||||
onSelected: function (key) {
|
||||
Settings.data.screenRecorder.audioCodec = key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Modules/SettingsPanel/Tabs/TimeWeatherTab.qml
Normal file
131
Modules/SettingsPanel/Tabs/TimeWeatherTab.qml
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginTiny * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Location"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Location section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginMedium * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
NTextInput {
|
||||
label: "Location name"
|
||||
description: "Choose a known location near you"
|
||||
text: Settings.data.location.name
|
||||
placeholderText: "Enter the location name"
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: {
|
||||
Settings.data.location.name = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// Time section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Time Format"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use 12-Hour Clock"
|
||||
description: "Display time in 12-hour format (AM/PM) instead of 24-hour"
|
||||
value: Settings.data.location.use12HourClock
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.location.use12HourClock = newValue
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Reverse Day/Month"
|
||||
description: "Display date as DD/MM instead of MM/DD"
|
||||
value: Settings.data.location.reverseDayMonth
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.location.reverseDayMonth = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// Weather section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginMedium * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Weather"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use Fahrenheit"
|
||||
description: "Display temperature in Fahrenheit instead of Celsius"
|
||||
value: Settings.data.location.useFahrenheit
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.location.useFahrenheit = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
246
Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml
Normal file
246
Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Qt.labs.folderlistmodel
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
property real scaling: 1
|
||||
readonly property string tabIcon: "photo_library"
|
||||
readonly property string tabLabel: "Wallpaper Selector"
|
||||
readonly property int tabIndex: 7
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
|
||||
contentWidth: parent.width
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.margins: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Current wallpaper display
|
||||
NText {
|
||||
text: "Current Wallpaper"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 120 * scaling
|
||||
radius: Style.radiusMedium * scaling
|
||||
color: Colors.mSurface
|
||||
border.color: Colors.mOutline
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
clip: true
|
||||
|
||||
NImageRounded {
|
||||
id: currentWallpaperImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginSmall * scaling
|
||||
imagePath: WallpapersService.currentWallpaper
|
||||
fallbackIcon: "image"
|
||||
borderColor: Colors.mOutline
|
||||
borderWidth: Math.max(1, Style.borderThin * scaling)
|
||||
imageRadius: Style.radiusMedium * scaling
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Wallpaper grid
|
||||
NText {
|
||||
text: "Wallpaper Selector"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Click on a wallpaper to set it as your current wallpaper"
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType
|
||||
+ " transition" : "Wallpapers will change instantly"
|
||||
color: Colors.mOnSurface
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
visible: Settings.data.wallpaper.swww.enabled
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh wallpaper list"
|
||||
onClicked: {
|
||||
WallpapersService.loadWallpapers()
|
||||
}
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper grid container
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
return Math.ceil(WallpapersService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
model: WallpapersService.wallpaperList
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickableDirection: Flickable.AutoFlickDirection
|
||||
interactive: false
|
||||
|
||||
property int columns: 5
|
||||
property int itemSize: Math.floor(
|
||||
(width - leftMargin - rightMargin - (4 * Style.marginSmall * scaling)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.67) + Style.marginSmall * scaling
|
||||
|
||||
leftMargin: Style.marginSmall * scaling
|
||||
rightMargin: Style.marginSmall * scaling
|
||||
topMargin: Style.marginSmall * scaling
|
||||
bottomMargin: Style.marginSmall * scaling
|
||||
|
||||
delegate: Rectangle {
|
||||
id: wallpaperItem
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: wallpaperPath === WallpapersService.currentWallpaper
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
height: Math.floor(wallpaperGridView.itemSize * 0.67)
|
||||
radius: Style.radiusMedium * scaling
|
||||
color: isSelected ? Colors.mPrimary : Colors.mSurface
|
||||
border.color: isSelected ? Colors.mSecondary : Colors.mOutline
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
clip: true
|
||||
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginTiny * scaling
|
||||
imagePath: wallpaperPath
|
||||
fallbackIcon: "image"
|
||||
|
||||
imageRadius: Style.radiusMedium * scaling
|
||||
}
|
||||
|
||||
// Selection indicator
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginTiny * scaling
|
||||
width: 20 * scaling
|
||||
height: 20 * scaling
|
||||
radius: width / 2
|
||||
color: Colors.mPrimary
|
||||
border.color: Colors.mOutline
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
visible: isSelected
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: "check"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Hover effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.mOnSurface
|
||||
opacity: mouseArea.containsMouse ? 0.1 : 0
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
WallpapersService.changeWallpaper(wallpaperPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.mSurface
|
||||
radius: Style.radiusMedium * scaling
|
||||
border.color: Colors.mOutline
|
||||
border.width: Math.max(1, Style.borderThin * scaling)
|
||||
visible: WallpapersService.wallpaperList.length === 0 && !WallpapersService.scanning
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginMedium * scaling
|
||||
|
||||
NText {
|
||||
text: "folder_open"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pointSize: Style.fontSizeLarge * scaling
|
||||
color: Colors.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "No wallpapers found"
|
||||
color: Colors.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Make sure your wallpaper directory is configured and contains image files"
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
350
Modules/SettingsPanel/Tabs/WallpaperTab.qml
Normal file
350
Modules/SettingsPanel/Tabs/WallpaperTab.qml
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginMedium * scaling
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Directory"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Wallpaper Settings Category
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
|
||||
// Wallpaper Folder
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
label: "Wallpaper Directory"
|
||||
description: "Path to your wallpaper directory"
|
||||
text: Settings.data.wallpaper.directory
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: {
|
||||
Settings.data.wallpaper.directory = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Automation"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Random Wallpaper
|
||||
NToggle {
|
||||
label: "Random Wallpaper"
|
||||
description: "Automatically select random wallpapers from the folder"
|
||||
value: Settings.data.wallpaper.isRandom
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.wallpaper.isRandom = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// Interval
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
NText {
|
||||
text: "Wallpaper Interval"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "How often to change wallpapers automatically (in seconds)"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: sliderWpInterval.value + " seconds"
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
id: sliderWpInterval
|
||||
Layout.fillWidth: true
|
||||
from: 10
|
||||
to: 900
|
||||
stepSize: 10
|
||||
value: Settings.data.wallpaper.randomInterval
|
||||
onPressedChanged: Settings.data.wallpaper.randomInterval = Math.round(value)
|
||||
cutoutColor: Colors.mSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginLarge * 2 * scaling
|
||||
Layout.bottomMargin: Style.marginLarge * scaling
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// SWWW
|
||||
ColumnLayout {
|
||||
spacing: Style.marginLarge * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "SWWW"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
Layout.bottomMargin: Style.marginSmall * scaling
|
||||
}
|
||||
|
||||
// Use SWWW
|
||||
NToggle {
|
||||
label: "Use SWWW"
|
||||
description: "Use SWWW daemon for advanced wallpaper management"
|
||||
value: Settings.data.wallpaper.swww.enabled
|
||||
onToggled: function (newValue) {
|
||||
Settings.data.wallpaper.swww.enabled = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// SWWW Settings (only visible when useSWWW is enabled)
|
||||
ColumnLayout {
|
||||
spacing: Style.marginSmall * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginSmall * scaling
|
||||
visible: Settings.data.wallpaper.swww.enabled
|
||||
|
||||
// Resize Mode
|
||||
NComboBox {
|
||||
label: "Resize Mode"
|
||||
description: "How SWWW should resize wallpapers to fit the screen"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "no"
|
||||
name: "No"
|
||||
}
|
||||
ListElement {
|
||||
key: "crop"
|
||||
name: "Crop"
|
||||
}
|
||||
ListElement {
|
||||
key: "fit"
|
||||
name: "Fit"
|
||||
}
|
||||
ListElement {
|
||||
key: "stretch"
|
||||
name: "Stretch"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.wallpaper.swww.resizeMethod
|
||||
onSelected: function (key) {
|
||||
Settings.data.wallpaper.swww.resizeMethod = key
|
||||
}
|
||||
}
|
||||
|
||||
// Transition Type
|
||||
NComboBox {
|
||||
label: "Transition Type"
|
||||
description: "Animation type when switching between wallpapers"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "none"
|
||||
name: "None"
|
||||
}
|
||||
ListElement {
|
||||
key: "simple"
|
||||
name: "Simple"
|
||||
}
|
||||
ListElement {
|
||||
key: "fade"
|
||||
name: "Fade"
|
||||
}
|
||||
ListElement {
|
||||
key: "left"
|
||||
name: "Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "right"
|
||||
name: "Right"
|
||||
}
|
||||
ListElement {
|
||||
key: "top"
|
||||
name: "Top"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom"
|
||||
name: "Bottom"
|
||||
}
|
||||
ListElement {
|
||||
key: "wipe"
|
||||
name: "Wipe"
|
||||
}
|
||||
ListElement {
|
||||
key: "wave"
|
||||
name: "Wave"
|
||||
}
|
||||
ListElement {
|
||||
key: "grow"
|
||||
name: "Grow"
|
||||
}
|
||||
ListElement {
|
||||
key: "center"
|
||||
name: "Center"
|
||||
}
|
||||
ListElement {
|
||||
key: "any"
|
||||
name: "Any"
|
||||
}
|
||||
ListElement {
|
||||
key: "outer"
|
||||
name: "Outer"
|
||||
}
|
||||
ListElement {
|
||||
key: "random"
|
||||
name: "Random"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.wallpaper.swww.transitionType
|
||||
onSelected: function (key) {
|
||||
Settings.data.wallpaper.swww.transitionType = key
|
||||
}
|
||||
}
|
||||
|
||||
// Transition FPS
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
NText {
|
||||
text: "Transition FPS"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Frames per second for transition animations"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: sliderWpTransitionFps.value + " FPS"
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
id: sliderWpTransitionFps
|
||||
Layout.fillWidth: true
|
||||
from: 30
|
||||
to: 500
|
||||
stepSize: 5
|
||||
value: Settings.data.wallpaper.swww.transitionFps
|
||||
onPressedChanged: Settings.data.wallpaper.swww.transitionFps = Math.round(value)
|
||||
cutoutColor: Colors.mSurface
|
||||
}
|
||||
}
|
||||
|
||||
// Transition Duration
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
NText {
|
||||
text: "Transition Duration"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Colors.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Duration of transition animations in seconds"
|
||||
font.pointSize: Style.fontSizeSmall * scaling
|
||||
color: Colors.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: sliderWpTransitionDuration.value.toFixed(2) + "s"
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
id: sliderWpTransitionDuration
|
||||
Layout.fillWidth: true
|
||||
from: 0.25
|
||||
to: 10
|
||||
stepSize: 0.05
|
||||
value: Settings.data.wallpaper.swww.transitionDuration
|
||||
onPressedChanged: Settings.data.wallpaper.swww.transitionDuration = value
|
||||
cutoutColor: Colors.mSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue