Settings rework...

This commit is contained in:
Ly-sec 2025-08-05 17:41:08 +02:00
parent 74b233798d
commit fb68300746
63 changed files with 7139 additions and 1026 deletions

View file

@ -0,0 +1,369 @@
import Quickshell
import Quickshell.Wayland
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
import qs.Settings
import qs.Widgets.SettingsWindow.Tabs
PanelWindow {
id: panelMain
implicitHeight: screen.height / 2
implicitWidth: screen.width / 2
color: "transparent"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
Component {
id: generalSettings
General {}
}
Component {
id: barSettings
Bar {}
}
Component {
id: timeWeatherSettings
TimeWeather {}
}
Component {
id: recordingSettings
Recording {}
}
Component {
id: networkSettings
Network {}
}
Component {
id: miscSettings
Misc {}
}
Component {
id: aboutSettings
About {}
}
Component {
id: displaySettings
Display {}
}
Rectangle {
id: background
color: Theme.backgroundPrimary
anchors.fill: parent
radius: 20
border.color: Theme.outline
border.width: 1
MultiEffect {
source: background
anchors.fill: background
shadowEnabled: true
shadowColor: Theme.shadow
shadowOpacity: 0.3
shadowHorizontalOffset: 0
shadowVerticalOffset: 2
shadowBlur: 12
}
}
Rectangle {
id: settings
color: Theme.backgroundTertiary
anchors {
left: tabs.right
top: parent.top
bottom: parent.bottom
right: parent.right
margins: 12
}
topRightRadius: 20
bottomRightRadius: 20
Rectangle {
id: headerArea
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 16
}
height: 48
color: "transparent"
RowLayout {
anchors.fill: parent
spacing: 12
Text {
id: tabName
text: "General"
font.pixelSize: 18
font.bold: true
color: Theme.textPrimary
Layout.fillWidth: true
}
Rectangle {
width: 32
height: 32
radius: 16
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
border.color: Theme.accentPrimary
border.width: 1
Text {
anchors.centerIn: parent
text: "close"
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
font.pixelSize: 18
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
}
MouseArea {
id: closeButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: panelMain.visible = false
}
}
}
}
Rectangle {
anchors {
top: headerArea.bottom
left: parent.left
right: parent.right
margins: 16
}
height: 1
color: Theme.outline
opacity: 0.3
}
Item {
id: settingsContainer
anchors {
top: headerArea.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 24
topMargin: 32
}
Loader {
id: settingsLoader
anchors.fill: parent
sourceComponent: generalSettings
opacity: 1
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
}
Loader {
id: settingsLoader2
anchors.fill: parent
opacity: 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
}
}
}
Rectangle {
id: tabs
color: Theme.surface
width: screen.width / 9
height: panelMain.height
topLeftRadius: 20
bottomLeftRadius: 20
border.color: Theme.outline
border.width: 1
Column {
width: parent.width
spacing: 0
topPadding: 8
Repeater {
id: repeater
model: [
{ icon: "tune", text: "General" },
{ icon: "space_dashboard", text: "Bar" },
{ icon: "schedule", text: "Time & Weather" },
{ icon: "photo_camera", text: "Recording" },
{ icon: "wifi", text: "Network" },
{ icon: "monitor", text: "Display" },
{ icon: "settings_suggest", text: "Misc" },
{ icon: "info", text: "About" }
]
delegate: Column {
width: tabs.width
height: 40
Item {
width: parent.width
height: 39
RowLayout {
anchors.fill: parent
spacing: 8
Rectangle {
id: activeIndicator
Layout.leftMargin: 8
Layout.preferredWidth: 3
Layout.preferredHeight: 24
Layout.alignment: Qt.AlignVCenter
radius: 2
color: Theme.accentPrimary
opacity: index === 0 ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 200 } }
}
Label {
id: icon
text: modelData.icon
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: index === 0 ? Theme.accentPrimary : Theme.textPrimary
opacity: index === 0 ? 1 : 0.8
Layout.leftMargin: 20
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.alignment: Qt.AlignVCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Label {
id: label
text: modelData.text
font.pixelSize: 12
color: index === 0 ? Theme.accentPrimary : Theme.textSecondary
font.weight: index === 0 ? Font.DemiBold : Font.Normal
Layout.fillWidth: true
Layout.preferredHeight: 24
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.leftMargin: 4
Layout.rightMargin: 16
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
const newComponent = {
0: generalSettings,
1: barSettings,
2: timeWeatherSettings,
3: recordingSettings,
4: networkSettings,
5: displaySettings,
6: miscSettings,
7: aboutSettings
}[index];
const tabNames = [
"General",
"Bar",
"Time & Weather",
"Recording",
"Network",
"Display",
"Misc",
"About"
];
tabName.text = tabNames[index];
if (settingsLoader.opacity === 1) {
settingsLoader2.sourceComponent = newComponent;
settingsLoader.opacity = 0;
settingsLoader2.opacity = 1;
} else {
settingsLoader.sourceComponent = newComponent;
settingsLoader2.opacity = 0;
settingsLoader.opacity = 1;
}
for (let i = 0; i < repeater.count; i++) {
let item = repeater.itemAt(i);
if (item) {
let containerItem = item.children[0];
let rowLayout = containerItem.children[0];
let indicator = rowLayout.children[0];
let icon = rowLayout.children[1];
let label = rowLayout.children[2];
indicator.opacity = i === index ? 1 : 0;
icon.color = i === index ? Theme.accentPrimary : Theme.textPrimary;
icon.opacity = i === index ? 1 : 0.8;
label.color = i === index ? Theme.accentPrimary : Theme.textSecondary;
label.font.weight = i === index ? Font.Bold : Font.Normal;
}
}
}
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.6
visible: index < (repeater.count - 1)
}
}
}
}
}
}

View file

@ -0,0 +1,405 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import qs.Settings
import qs.Components
Item {
id: root
property string latestVersion: "Unknown"
property string currentVersion: "Unknown"
property var contributors: []
property string githubDataPath: Settings.settingsDir + "github_data.json"
Process {
id: currentVersionProcess
command: ["sh", "-c", "cd " + Quickshell.shellDir + " && git describe --tags --abbrev=0 2>/dev/null || echo 'Unknown'"]
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
}
}
}
Component.onCompleted: {
running = true
}
}
FileView {
id: githubDataFile
path: root.githubDataPath
blockLoading: true
printErrors: true
watchChanges: true
JsonAdapter {
id: githubData
property string version: "Unknown"
property var contributors: []
property double timestamp: 0
}
onFileChanged: githubDataFile.reload()
onLoaded: loadFromFile()
onLoadFailed: function(error) {
console.log("GitHub data file doesn't exist yet, creating it...")
githubData.version = "Unknown"
githubData.contributors = []
githubData.timestamp = 0
githubDataFile.writeAdapter()
fetchFromGitHub()
}
Component.onCompleted: if (path) reload()
}
function loadFromFile() {
const now = Date.now()
const data = githubData
if (!data.timestamp || (now - data.timestamp > 3600000)) {
console.log("[About] Cache expired or missing, fetching new data from GitHub...")
fetchFromGitHub()
return
}
console.log("[About] Loading cached GitHub data (age: " + Math.round((now - data.timestamp) / 60000) + " minutes)")
if (data.version) {
root.latestVersion = data.version
}
if (data.contributors) {
root.contributors = data.contributors
}
}
Process {
id: versionProcess
command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/releases/latest"]
stdout: StdioCollector {
onStreamFinished: {
try {
const data = JSON.parse(text)
if (data.tag_name) {
const version = data.tag_name
githubData.version = version
root.latestVersion = version
console.log("[About] Latest version fetched from GitHub:", version)
} else {
console.log("No tag_name in GitHub response")
}
saveData()
} catch (e) {
console.error("Failed to parse version:", e)
}
}
}
}
Process {
id: contributorsProcess
command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/contributors?per_page=100"]
stdout: StdioCollector {
onStreamFinished: {
try {
const data = JSON.parse(text)
githubData.contributors = data || []
root.contributors = githubData.contributors
console.log("[About] Contributors data fetched from GitHub:", githubData.contributors.length, "contributors")
saveData()
} catch (e) {
console.error("Failed to parse contributors:", e)
root.contributors = []
}
}
}
}
function fetchFromGitHub() {
versionProcess.running = true
contributorsProcess.running = true
}
function saveData() {
githubData.timestamp = Date.now()
Qt.callLater(() => {
githubDataFile.writeAdapter()
})
}
Item {
anchors.fill: parent
ColumnLayout {
id: mainLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 8
Item {
Layout.fillWidth: true
Layout.preferredHeight: 32
}
Text {
text: "Noctalia"
font.pixelSize: 24
font.bold: true
color: Theme.textPrimary
Layout.alignment: Qt.AlignCenter
}
GridLayout {
Layout.alignment: Qt.AlignCenter
columns: 2
rowSpacing: 4
columnSpacing: 8
Text {
text: "Latest Version:"
font.pixelSize: 16
color: Theme.textSecondary
Layout.alignment: Qt.AlignRight
}
Text {
text: root.latestVersion
font.pixelSize: 16
color: Theme.textPrimary
font.bold: true
}
Text {
text: "Installed Version:"
font.pixelSize: 16
color: Theme.textSecondary
Layout.alignment: Qt.AlignRight
}
Text {
text: root.currentVersion
font.pixelSize: 16
color: Theme.textPrimary
font.bold: true
}
}
Rectangle {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 8
Layout.preferredWidth: updateText.implicitWidth + 46
Layout.preferredHeight: 32
radius: 20
color: updateArea.containsMouse ? Theme.accentPrimary : "transparent"
border.color: Theme.accentPrimary
border.width: 1
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 (let 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: 8
Text {
text: "system_update"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: updateArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
}
Text {
id: updateText
text: "Download latest release"
font.pixelSize: 14
color: updateArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
}
}
MouseArea {
id: updateArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
}
}
}
Text {
text: "Description something something <.< I hate writing text..."
font.pixelSize: 14
color: Theme.textSecondary
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 16
}
ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 32
Layout.rightMargin: 32
spacing: 16
RowLayout {
Layout.alignment: Qt.AlignCenter
spacing: 8
Text {
text: "Contributors"
font.pixelSize: 18
font.bold: true
color: Theme.textPrimary
}
Text {
text: "(" + root.contributors.length + ")"
font.pixelSize: 14
color: Theme.textSecondary
}
}
ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: 300
clip: true
Item {
anchors.fill: parent
GridView {
id: contributorsGrid
anchors.centerIn: parent
width: Math.min(parent.width, Math.ceil(root.contributors.length / 3) * 200)
height: parent.height
cellWidth: 200
cellHeight: 110
model: root.contributors
delegate: Rectangle {
width: contributorsGrid.cellWidth - 4
height: contributorsGrid.cellHeight - 10
radius: 20
color: contributorArea.containsMouse ? Theme.highlight : "transparent"
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 12
Item {
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Image {
id: avatarImage
anchors.fill: parent
source: modelData.avatar_url || ""
sourceSize: Qt.size(80, 80)
visible: false
mipmap: true
smooth: true
asynchronous: true
fillMode: Image.PreserveAspectCrop
cache: true
}
MultiEffect {
anchors.fill: parent
source: avatarImage
maskEnabled: true
maskSource: mask
}
Item {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
radius: avatarImage.width / 2
}
}
Text {
anchors.centerIn: parent
text: "person"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textPrimary
visible: !avatarImage.source || avatarImage.status !== Image.Ready
}
}
ColumnLayout {
spacing: 4
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Text {
text: modelData.login || "Unknown"
font.pixelSize: 13
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textPrimary
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: (modelData.contributions || 0) + " commits"
font.pixelSize: 11
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textSecondary
}
}
}
MouseArea {
id: contributorArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData.html_url) {
Quickshell.execDetached(["xdg-open", modelData.html_url])
}
}
}
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,380 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Bar Elements"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Active Window Icon"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display the icon of the currently focused window in the bar"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: activeWindowIconSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showActiveWindowIcon ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showActiveWindowIcon ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: activeWindowIconThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showActiveWindowIcon ? activeWindowIconSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showActiveWindowIcon = !Settings.settings.showActiveWindowIcon;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Active Window"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display the title of the currently focused window below the bar"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: activeWindowSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showActiveWindow ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showActiveWindow ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: activeWindowThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showActiveWindow ? activeWindowSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showActiveWindow = !Settings.settings.showActiveWindow;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show System Info"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display system information (CPU, RAM, etc.) in the bar"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: systemInfoSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showSystemInfoInBar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showSystemInfoInBar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: systemInfoThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showSystemInfoInBar ? systemInfoSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showSystemInfoInBar = !Settings.settings.showSystemInfoInBar;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Taskbar"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display a taskbar showing currently open windows"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: taskbarSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showTaskbar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showTaskbar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: taskbarThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showTaskbar ? taskbarSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showTaskbar = !Settings.settings.showTaskbar;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Media"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display media controls and information in the bar"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: mediaSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showMediaInBar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showMediaInBar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: mediaThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showMediaInBar ? mediaSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showMediaInBar = !Settings.settings.showMediaInBar;
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}

View file

@ -0,0 +1,97 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
Rectangle {
id: root
width: 64
height: 32
radius: 16
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
property bool useFahrenheit: Settings.settings.useFahrenheit
Rectangle {
id: slider
width: parent.width / 2 - 4
height: parent.height - 4
radius: 14
color: Theme.accentPrimary
x: 2 + (useFahrenheit ? parent.width / 2 : 0)
y: 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Row {
anchors.fill: parent
spacing: 0
Item {
width: parent.width / 2
height: parent.height
Text {
anchors.centerIn: parent
text: "°C"
font.pixelSize: 13
font.bold: !useFahrenheit
color: !useFahrenheit ? Theme.onAccent : Theme.textPrimary
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (useFahrenheit) {
Settings.settings.useFahrenheit = false;
}
}
}
}
Item {
width: parent.width / 2
height: parent.height
Text {
anchors.centerIn: parent
text: "°F"
font.pixelSize: 13
font.bold: useFahrenheit
color: useFahrenheit ? Theme.onAccent : Theme.textPrimary
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!useFahrenheit) {
Settings.settings.useFahrenheit = true;
}
}
}
}
}
}

View file

@ -0,0 +1,354 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
// Get list of available monitors/screens
property var monitors: Quickshell.screens || []
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Monitor Selection"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Bar Monitors"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Select which monitors to display the top panel/bar on"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
Flow {
Layout.fillWidth: true
spacing: 8
Repeater {
model: root.monitors
delegate: Rectangle {
id: barCheckbox
property bool isChecked: false
Component.onCompleted: {
// Initialize checkbox state from settings
let monitors = Settings.settings.barMonitors || [];
isChecked = monitors.includes(modelData.name);
}
width: checkboxContent.implicitWidth + 16
height: 32
radius: 16
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
border.color: isChecked ? Theme.accentPrimary : Theme.outline
border.width: 1
RowLayout {
id: checkboxContent
anchors.centerIn: parent
spacing: 6
Text {
text: barCheckbox.isChecked ? "check" : ""
font.family: "Material Symbols Outlined"
font.pixelSize: 14
color: barCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
visible: barCheckbox.isChecked
}
Text {
text: modelData.name || "Unknown"
font.pixelSize: 12
color: barCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
isChecked = !isChecked;
// Update settings array when checkbox is toggled
let monitors = Settings.settings.barMonitors || [];
monitors = [...monitors]; // Create copy to trigger reactivity
if (isChecked) {
if (!monitors.includes(modelData.name)) {
monitors.push(modelData.name);
}
} else {
monitors = monitors.filter(name => name !== modelData.name);
}
Settings.settings.barMonitors = monitors;
console.log("Bar monitors updated:", JSON.stringify(monitors));
}
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Dock Monitors"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Select which monitors to display the application dock on"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
Flow {
Layout.fillWidth: true
spacing: 8
Repeater {
model: root.monitors
delegate: Rectangle {
id: dockCheckbox
property bool isChecked: false
Component.onCompleted: {
// Initialize with current settings
let monitors = Settings.settings.dockMonitors || [];
isChecked = monitors.includes(modelData.name);
}
width: checkboxContent.implicitWidth + 16
height: 32
radius: 16
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
border.color: isChecked ? Theme.accentPrimary : Theme.outline
border.width: 1
RowLayout {
id: checkboxContent
anchors.centerIn: parent
spacing: 6
Text {
text: dockCheckbox.isChecked ? "check" : ""
font.family: "Material Symbols Outlined"
font.pixelSize: 14
color: dockCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
visible: dockCheckbox.isChecked
}
Text {
text: modelData.name || "Unknown"
font.pixelSize: 12
color: dockCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Toggle state immediately for UI responsiveness
isChecked = !isChecked;
// Update settings
let monitors = Settings.settings.dockMonitors || [];
monitors = [...monitors]; // Copy array
if (isChecked) {
// Add to array if not already there
if (!monitors.includes(modelData.name)) {
monitors.push(modelData.name);
}
} else {
// Remove from array
monitors = monitors.filter(name => name !== modelData.name);
}
Settings.settings.dockMonitors = monitors;
console.log("Dock monitors updated:", JSON.stringify(monitors));
}
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Notification Monitors"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Select which monitors to display system notifications on"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
Flow {
Layout.fillWidth: true
spacing: 8
Repeater {
model: root.monitors
delegate: Rectangle {
id: notificationCheckbox
property bool isChecked: false
Component.onCompleted: {
// Initialize with current settings
let monitors = Settings.settings.notificationMonitors || [];
isChecked = monitors.includes(modelData.name);
}
width: checkboxContent.implicitWidth + 16
height: 32
radius: 16
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
border.color: isChecked ? Theme.accentPrimary : Theme.outline
border.width: 1
RowLayout {
id: checkboxContent
anchors.centerIn: parent
spacing: 6
Text {
text: notificationCheckbox.isChecked ? "check" : ""
font.family: "Material Symbols Outlined"
font.pixelSize: 14
color: notificationCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
visible: notificationCheckbox.isChecked
}
Text {
text: modelData.name || "Unknown"
font.pixelSize: 12
color: notificationCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Toggle state immediately for UI responsiveness
isChecked = !isChecked;
// Update settings
let monitors = Settings.settings.notificationMonitors || [];
monitors = [...monitors]; // Copy array
if (isChecked) {
// Add to array if not already there
if (!monitors.includes(modelData.name)) {
monitors.push(modelData.name);
}
} else {
// Remove from array
monitors = monitors.filter(name => name !== modelData.name);
}
Settings.settings.notificationMonitors = monitors;
console.log("Notification monitors updated:", JSON.stringify(monitors));
}
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}

View file

@ -0,0 +1,339 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Profile"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 2
Layout.fillWidth: true
Text {
text: "Profile Image"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Your profile picture displayed in various places throughout the shell"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.bottomMargin: 4
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Rectangle {
width: 48
height: 48
radius: 24
Rectangle {
anchors.fill: parent
color: "transparent"
radius: 24
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 2
z: 2
}
Avatar {}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: profileImageInput
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.profileImage
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhUrlCharactersOnly
onTextChanged: {
Settings.settings.profileImage = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: profileImageInput.forceActiveFocus()
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 16
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "User Interface"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Corners"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display rounded corners on screen edges"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: cornersSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showCorners ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showCorners ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: cornersThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showCorners ? cornersSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showCorners = !Settings.settings.showCorners;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 4
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Dock"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display a dock at the bottom of the screen for quick access to applications"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: dockSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showDock ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showDock ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: dockThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showDock ? dockSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showDock = !Settings.settings.showDock;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 4
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Dim Desktop"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Dim the desktop when panels or menus are open"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: dimSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.dimPanels ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.dimPanels ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: dimThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.dimPanels ? dimSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.dimPanels = !Settings.settings.dimPanels;
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}

View file

@ -0,0 +1,137 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Media"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "Visualizer Type"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Choose the style of the audio visualizer"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.bottomMargin: 4
}
ComboBox {
id: visualizerTypeComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["radial", "fire", "diamond"]
currentIndex: model.indexOf(Settings.settings.visualizerType)
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: visualizerTypeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: visualizerTypeComboBox.indicator.width + visualizerTypeComboBox.spacing
text: visualizerTypeComboBox.displayText.charAt(0).toUpperCase() + visualizerTypeComboBox.displayText.slice(1)
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: visualizerTypeComboBox.width - width - 12
y: visualizerTypeComboBox.topPadding + (visualizerTypeComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: visualizerTypeComboBox.height
width: visualizerTypeComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: visualizerTypeComboBox.popup.visible ? visualizerTypeComboBox.delegateModel : null
currentIndex: visualizerTypeComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: visualizerTypeComboBox.width
contentItem: Text {
text: modelData.charAt(0).toUpperCase() + modelData.slice(1)
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: visualizerTypeComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.visualizerType = model[index];
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}

View file

@ -0,0 +1,193 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 24
Component.onCompleted: {
Quickshell.execDetached(["nmcli", "-t", "-f", "WIFI", "radio"])
}
ColumnLayout {
spacing: 16
Layout.fillWidth: true
Text {
text: "Wi-Fi"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Enable Wi-Fi"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Turn Wi-Fi radio on or off"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: wifiSwitch
width: 52
height: 32
radius: 16
property bool checked: Settings.settings.wifiEnabled
color: checked ? Theme.accentPrimary : Theme.surfaceVariant
border.color: checked ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: wifiThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: wifiSwitch.checked ? wifiSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.wifiEnabled = !Settings.settings.wifiEnabled
Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.settings.wifiEnabled ? "on" : "off"])
}
}
}
}
}
}
ColumnLayout {
spacing: 16
Layout.fillWidth: true
Layout.topMargin: 16
Text {
text: "Bluetooth"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Enable Bluetooth"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Turn Bluetooth radio on or off"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: bluetoothSwitch
width: 52
height: 32
radius: 16
property bool checked: Settings.settings.bluetoothEnabled
color: checked ? Theme.accentPrimary : Theme.surfaceVariant
border.color: checked ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: bluetoothThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: bluetoothSwitch.checked ? bluetoothSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (Bluetooth.defaultAdapter) {
Settings.settings.bluetoothEnabled = !Settings.settings.bluetoothEnabled
Bluetooth.defaultAdapter.enabled = Settings.settings.bluetoothEnabled
if (Bluetooth.defaultAdapter.enabled) {
Bluetooth.defaultAdapter.discovering = true
}
}
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}

View file

@ -0,0 +1,19 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 24
Text {
text: "Coming soon..."
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 32
}
}

View file

@ -0,0 +1,812 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
padding: 0
rightPadding: 12
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: 4
Layout.fillWidth: true
Text {
text: "Screen Recording"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "Output Directory"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Directory where screen recordings will be saved"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: videoPathInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: videoPathInput
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.videoPath !== undefined ? Settings.settings.videoPath : ""
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhUrlCharactersOnly
onTextChanged: {
Settings.settings.videoPath = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: videoPathInput.forceActiveFocus()
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Frame Rate"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Target frame rate for screen recordings (default: 60)"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
SpinBox {
id: frameRateSpinBox
Layout.fillWidth: true
Layout.preferredHeight: 40
from: 24
to: 144
value: Settings.settings.recordingFrameRate || 60
stepSize: 1
onValueChanged: {
Settings.settings.recordingFrameRate = value;
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: frameRateSpinBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: TextInput {
text: frameRateSpinBox.textFromValue(frameRateSpinBox.value, frameRateSpinBox.locale)
font.pixelSize: 13
color: Theme.textPrimary
selectionColor: Theme.accentPrimary
selectedTextColor: Theme.onAccent
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
readOnly: false
selectByMouse: true
validator: IntValidator {
bottom: frameRateSpinBox.from
top: frameRateSpinBox.to
}
inputMethodHints: Qt.ImhDigitsOnly
onTextChanged: {
var newValue = parseInt(text);
if (!isNaN(newValue) && newValue >= frameRateSpinBox.from && newValue <= frameRateSpinBox.to) {
frameRateSpinBox.value = newValue;
}
}
onEditingFinished: {
var newValue = parseInt(text);
if (isNaN(newValue) || newValue < frameRateSpinBox.from || newValue > frameRateSpinBox.to) {
text = frameRateSpinBox.textFromValue(frameRateSpinBox.value, frameRateSpinBox.locale);
}
}
}
up.indicator: Rectangle {
x: parent.width - width
height: parent.height
width: height
color: "transparent"
radius: 16
Text {
text: "add"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Theme.textPrimary
anchors.centerIn: parent
}
}
down.indicator: Rectangle {
x: 0
height: parent.height
width: height
color: "transparent"
radius: 16
Text {
text: "remove"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Theme.textPrimary
anchors.centerIn: parent
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Audio Source"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Audio source to capture during recording"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
ComboBox {
id: audioSourceComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["default_output", "default_input", "both"]
currentIndex: model.indexOf(Settings.settings.recordingAudioSource || "default_output")
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: audioSourceComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: audioSourceComboBox.indicator.width + audioSourceComboBox.spacing
text: {
switch(audioSourceComboBox.currentText) {
case "default_output": return "System Audio";
case "default_input": return "Microphone";
case "both": return "System Audio + Microphone";
default: return audioSourceComboBox.currentText;
}
}
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: audioSourceComboBox.width - width - 12
y: audioSourceComboBox.topPadding + (audioSourceComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: audioSourceComboBox.height
width: audioSourceComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: audioSourceComboBox.popup.visible ? audioSourceComboBox.delegateModel : null
currentIndex: audioSourceComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: audioSourceComboBox.width
contentItem: Text {
text: {
switch(modelData) {
case "default_output": return "System Audio";
case "default_input": return "Microphone";
case "both": return "System Audio + Microphone";
default: return modelData;
}
}
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: audioSourceComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.recordingAudioSource = model[index];
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Video Quality"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Higher quality results in larger file sizes"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
ComboBox {
id: qualityComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["medium", "high", "very_high", "ultra"]
currentIndex: model.indexOf(Settings.settings.recordingQuality || "very_high")
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: qualityComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: qualityComboBox.indicator.width + qualityComboBox.spacing
text: {
switch(qualityComboBox.currentText) {
case "medium": return "Medium";
case "high": return "High";
case "very_high": return "Very High";
case "ultra": return "Ultra";
default: return qualityComboBox.currentText;
}
}
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: qualityComboBox.width - width - 12
y: qualityComboBox.topPadding + (qualityComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: qualityComboBox.height
width: qualityComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: qualityComboBox.popup.visible ? qualityComboBox.delegateModel : null
currentIndex: qualityComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: qualityComboBox.width
contentItem: Text {
text: {
switch(modelData) {
case "medium": return "Medium";
case "high": return "High";
case "very_high": return "Very High";
case "ultra": return "Ultra";
default: return modelData;
}
}
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: qualityComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.recordingQuality = model[index];
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Video Codec"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Different codecs offer different compression and compatibility"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
ComboBox {
id: codecComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["h264", "hevc", "av1", "vp8", "vp9"]
currentIndex: model.indexOf(Settings.settings.recordingCodec || "h264")
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: codecComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: codecComboBox.indicator.width + codecComboBox.spacing
text: codecComboBox.currentText.toUpperCase()
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: codecComboBox.width - width - 12
y: codecComboBox.topPadding + (codecComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: codecComboBox.height
width: codecComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: codecComboBox.popup.visible ? codecComboBox.delegateModel : null
currentIndex: codecComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: codecComboBox.width
contentItem: Text {
text: modelData.toUpperCase()
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: codecComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.recordingCodec = model[index];
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Audio Codec"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Opus is recommended for best performance and smallest audio size"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
ComboBox {
id: audioCodecComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["opus", "aac"]
currentIndex: model.indexOf(Settings.settings.audioCodec || "opus")
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: audioCodecComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: audioCodecComboBox.indicator.width + audioCodecComboBox.spacing
text: audioCodecComboBox.currentText.toUpperCase()
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: audioCodecComboBox.width - width - 12
y: audioCodecComboBox.topPadding + (audioCodecComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: audioCodecComboBox.height
width: audioCodecComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: audioCodecComboBox.popup.visible ? audioCodecComboBox.delegateModel : null
currentIndex: audioCodecComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: audioCodecComboBox.width
contentItem: Text {
text: modelData.toUpperCase()
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: audioCodecComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.audioCodec = model[index];
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Color Range"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Limited is recommended for better compatibility"
font.pixelSize: 12
color: Theme.textSecondary
Layout.bottomMargin: 4
}
ComboBox {
id: colorRangeComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["limited", "full"]
currentIndex: model.indexOf(Settings.settings.colorRange || "limited")
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: colorRangeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: colorRangeComboBox.indicator.width + colorRangeComboBox.spacing
text: colorRangeComboBox.currentText.charAt(0).toUpperCase() + colorRangeComboBox.currentText.slice(1)
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: colorRangeComboBox.width - width - 12
y: colorRangeComboBox.topPadding + (colorRangeComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: colorRangeComboBox.height
width: colorRangeComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: colorRangeComboBox.popup.visible ? colorRangeComboBox.delegateModel : null
currentIndex: colorRangeComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: colorRangeComboBox.width
contentItem: Text {
text: modelData.charAt(0).toUpperCase() + modelData.slice(1)
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: colorRangeComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.colorRange = model[index];
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Show Cursor"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Record mouse cursor in the video"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: cursorSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showCursor ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showCursor ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: cursorThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showCursor ? cursorSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showCursor = !Settings.settings.showCursor;
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 24
}
}
}
}

View file

@ -0,0 +1,283 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
import qs.Widgets.SettingsWindow.Tabs.Components
ColumnLayout {
id: root
spacing: 0
anchors.fill: parent
anchors.margins: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Time"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Use 12 Hour Clock"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display time in 12-hour format (e.g., 2:30 PM) instead of 24-hour format"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: use12HourClockSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: use12HourClockThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.use12HourClock ? use12HourClockSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.use12HourClock = !Settings.settings.use12HourClock;
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "US Style Date"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Display dates in MM/DD/YYYY format instead of DD/MM/YYYY"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: reverseDayMonthSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: reverseDayMonthThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.reverseDayMonth ? reverseDayMonthSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.reverseDayMonth = !Settings.settings.reverseDayMonth;
}
}
}
}
}
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Layout.topMargin: 16
Text {
text: "Weather"
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.bottomMargin: 8
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "City"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Your city name for weather information"
font.pixelSize: 12
color: Theme.textSecondary
Layout.fillWidth: true
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: cityInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: cityInput
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.weatherCity
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
focus: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
onTextChanged: {
Settings.settings.weatherCity = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: {
cityInput.forceActiveFocus();
}
}
}
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
spacing: 8
Layout.fillWidth: true
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Text {
text: "Temperature Unit"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Text {
text: "Choose between Celsius and Fahrenheit"
font.pixelSize: 12
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
UnitSelector {}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}