mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 09:36:46 +00:00
quickshell and hyprland additions
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
property int logoOffset: -30
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: 460
|
||||
spacing: Metrics.spacing(12)
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Metrics.fontSize(200)
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.osIcon
|
||||
anchors.centerIn: parent
|
||||
x: root.logoOffset
|
||||
font.pixelSize: Metrics.fontSize(200)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Nucleus Shell"
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: "Outfit ExtraBold"
|
||||
font.pixelSize: Metrics.fontSize(26)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "A shell built to get things done."
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
StyledButton {
|
||||
text: "View on GitHub"
|
||||
icon: "code"
|
||||
secondary: true
|
||||
onClicked: Qt.openUrlExternally("https://github.com/xZepyx/nucleus-shell")
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
text: "Report Issue"
|
||||
icon: "bug_report"
|
||||
secondary: true
|
||||
onClicked: Qt.openUrlExternally("https://github.com/xZepyx/nucleus-shell/issues")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Nucleus-Shell v" + Config.runtime.shell.version
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Metrics.margin(24)
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 52
|
||||
height: 52
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Metrics.margin(24)
|
||||
|
||||
StyledText {
|
||||
text: "↻"
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Metrics.fontSize(22)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
Globals.states.settingsOpen = false
|
||||
|
||||
Quickshell.execDetached(["notify-send", "Updating Nucleus Shell"])
|
||||
|
||||
Quickshell.execDetached([
|
||||
"kitty",
|
||||
"--hold",
|
||||
"bash",
|
||||
"-c",
|
||||
Directories.scriptsPath + "/system/update.sh"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
import qs.plugins
|
||||
|
||||
ContentMenu {
|
||||
title: "Appearance"
|
||||
description: "Adjust how the desktop looks like."
|
||||
|
||||
ContentCard {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(4)
|
||||
|
||||
StyledText {
|
||||
text: "Select Theme"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Choose between dark or light mode."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: "#888888"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: Metrics.margin(15)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
StyledButton {
|
||||
Layout.preferredHeight: 300
|
||||
Layout.preferredWidth: 460
|
||||
Layout.maximumHeight: 400
|
||||
Layout.maximumWidth: 500
|
||||
icon: "dark_mode"
|
||||
iconSize: Metrics.iconSize(64)
|
||||
checked: Config.runtime.appearance.theme === "dark"
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
if (!Config.runtime.appearance.colors.autogenerated) {
|
||||
const scheme = Config.runtime.appearance.colors.scheme
|
||||
const file = Theme.map[scheme]?.dark
|
||||
if (!file) {
|
||||
Theme.notifyMissingVariant(scheme, "dark")
|
||||
return
|
||||
}
|
||||
|
||||
Config.updateKey("appearance.theme", "dark")
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "theme", "switch", file
|
||||
])
|
||||
} else {
|
||||
Config.updateKey("appearance.theme", "dark")
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "ipc", "call", "global", "regenColors"
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
Layout.preferredHeight: 300
|
||||
Layout.preferredWidth: 460
|
||||
Layout.maximumHeight: 400
|
||||
Layout.maximumWidth: 500
|
||||
icon: "light_mode"
|
||||
iconSize: Metrics.iconSize(64)
|
||||
checked: Config.runtime.appearance.theme === "light"
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
if (!Config.runtime.appearance.colors.autogenerated) {
|
||||
const scheme = Config.runtime.appearance.colors.scheme
|
||||
const file = Theme.map[scheme]?.light
|
||||
if (!file) {
|
||||
Theme.notifyMissingVariant(scheme, "light")
|
||||
return
|
||||
}
|
||||
|
||||
Config.updateKey("appearance.theme", "light")
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "theme", "switch", file
|
||||
])
|
||||
} else {
|
||||
Config.updateKey("appearance.theme", "light")
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "ipc", "call", "global", "regenColors"
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Metrics.spacing(30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
RowLayout {
|
||||
opacity: autogeneratedColorsSelector.enabled ? 1 : 0.8
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Color Generation Schemes:"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Choose the scheme for autogenerated color generation."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
id: autogeneratedColorsSelector
|
||||
label: "Color Scheme"
|
||||
model: [
|
||||
"scheme-content",
|
||||
"scheme-expressive",
|
||||
"scheme-fidelity",
|
||||
"scheme-fruit-salad",
|
||||
"scheme-monochrome",
|
||||
"scheme-neutral",
|
||||
"scheme-rainbow",
|
||||
"scheme-tonal-spot"
|
||||
]
|
||||
|
||||
currentIndex: model.indexOf(Config.runtime.appearance.colors.matugenScheme)
|
||||
|
||||
onSelectedIndexChanged: (index) => {
|
||||
if (!Config.runtime.appearance.colors.autogenerated)
|
||||
return
|
||||
const selectedScheme = model[index]
|
||||
Config.updateKey("appearance.colors.matugenScheme", selectedScheme)
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "ipc", "call", "global", "regenColors"
|
||||
])
|
||||
}
|
||||
|
||||
enabled: Config.runtime.appearance.colors.autogenerated
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
opacity: predefinedThemeSelector.enabled ? 1 : 0.8
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
text: "Predefined/Custom Themes:"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
text: "Choose a pre-defined theme for your interface."
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
id: predefinedThemeSelector
|
||||
label: "Theme"
|
||||
model: Object.keys(Theme.map)
|
||||
currentIndex: model.indexOf(Config.runtime.appearance.colors.scheme)
|
||||
|
||||
onSelectedIndexChanged: (index) => {
|
||||
if (Config.runtime.appearance.colors.autogenerated)
|
||||
return
|
||||
const selectedTheme = model[index]
|
||||
const variant = Config.runtime.appearance.theme
|
||||
const file = Theme.map[selectedTheme][variant]
|
||||
if (!file) return
|
||||
|
||||
Config.updateKey("appearance.colors.scheme", selectedTheme)
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "theme", "switch", file
|
||||
])
|
||||
}
|
||||
|
||||
enabled: !Config.runtime.appearance.colors.autogenerated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledSwitchOption {
|
||||
title: "Tint Icons"
|
||||
description: "Either tint icons across the shell or keep them colorized."
|
||||
prefField: "appearance.tintIcons"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Use Autogenerated Themes"
|
||||
description: "Use autogenerated themes."
|
||||
prefField: "appearance.colors.autogenerated"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Use User Defined Themes"
|
||||
description: "Enabling this will also run the default `config.toml` in `~/.config/matugen` dir."
|
||||
prefField: "appearance.colors.runMatugenUserWide"
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Clock"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Show Clock"
|
||||
description: "Whether to show or disable the clock on the background."
|
||||
prefField: "appearance.background.clock.enabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Analog Variant"
|
||||
description: "Whether to use analog clock or not."
|
||||
prefField: "appearance.background.clock.isAnalog"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Rotate Clock Polygon"
|
||||
description: "Rotate the shape polygon of the analog clock."
|
||||
prefField: "appearance.background.clock.rotatePolygonBg"
|
||||
enabled: Config.runtime.appearance.background.clock.isAnalog
|
||||
opacity: enabled ? 1 : 0.8
|
||||
}
|
||||
|
||||
NumberStepper {
|
||||
label: "Rotation Duration"
|
||||
description: "Adjust the duration in which the clock rotates 360* (Seconds)."
|
||||
prefField: "appearance.background.clock.rotationDuration"
|
||||
minimum: 1
|
||||
maximum: 40
|
||||
step: 1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: shapeSelector
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Analog Clock Shape"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Choose the analog clock's shape."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
label: "Shape Type"
|
||||
model: ["Cookie 7 Sided", "Cookie 9 Sided", "Cookie 12 Sided", "Pixelated Circle", "Circle"]
|
||||
|
||||
currentIndex: Config.runtime.appearance.background.clock.shape
|
||||
|
||||
onSelectedIndexChanged: (index) => {
|
||||
Config.updateKey(
|
||||
"appearance.background.clock.shape",
|
||||
index
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Rounding"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Factor"
|
||||
description: "Adjust the rounding factor."
|
||||
prefField: "appearance.rounding.factor"
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
step: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Font"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Scale"
|
||||
description: "Adjust the font scale."
|
||||
prefField: "appearance.font.scale"
|
||||
minimum: 0.1
|
||||
maximum: 2
|
||||
step: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Transparency"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Whether to enable or disable transparency."
|
||||
prefField: "appearance.transparency.enabled"
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Factor"
|
||||
description: "Adjust the alpha value for transparency."
|
||||
prefField: "appearance.transparency.alpha"
|
||||
minimum: 0.1
|
||||
maximum: 1
|
||||
step: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Animations"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Whether to enable or disable animations (applies everywhere in the shell)."
|
||||
prefField: "appearance.animations.enabled"
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Duration Scale"
|
||||
description: "Adjust the duration scale of the animations."
|
||||
prefField: "appearance.animations.durationScale"
|
||||
minimum: 0.1
|
||||
maximum: 1
|
||||
step: 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import qs.modules.functions
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
title: "Sound"
|
||||
description: "Volume and audio devices"
|
||||
|
||||
ContentCard {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(20)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
radius: Metrics.radius("large")
|
||||
color: Appearance.m3colors.m3primaryContainer
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
icon: "volume_up"
|
||||
color: Appearance.m3colors.m3onPrimaryContainer
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
text: "Output"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Volume.defaultSink.description
|
||||
font.pixelSize: Metrics.fontSize(13)
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
opacity: 0.4
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
text: "Volume"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledText {
|
||||
animate: false
|
||||
text: Math.round(Volume.defaultSink.audio.volume * 100) + "%"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit SemiBold")
|
||||
color: Appearance.m3colors.m3primary
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
MaterialSymbol {
|
||||
icon: Volume.defaultSink.audio.muted ? "volume_off"
|
||||
: Volume.defaultSink.audio.volume < 0.33 ? "volume_mute"
|
||||
: Volume.defaultSink.audio.volume < 0.66 ? "volume_down"
|
||||
: "volume_up"
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
|
||||
StyledSlider {
|
||||
id: outputVolumeSlider
|
||||
Layout.fillWidth: true
|
||||
value: Volume.defaultSink.audio.volume * 100
|
||||
onValueChanged: Volume.setVolume(value / 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
StyledText {
|
||||
text: "Device"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledDropDown {
|
||||
Layout.fillWidth: true
|
||||
label: "Output device"
|
||||
model: Volume.sinks.map(d => d.description)
|
||||
currentIndex: {
|
||||
for (let i = 0; i < Volume.sinks.length; i++)
|
||||
if (Volume.sinks[i].name === Volume.defaultSink.name) return i
|
||||
return -1
|
||||
}
|
||||
onSelectedIndexChanged: index => {
|
||||
if (index >= 0 && index < Volume.sinks.length)
|
||||
Volume.setDefaultSink(Volume.sinks[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 56
|
||||
radius: Metrics.radius("small")
|
||||
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Metrics.margin(16)
|
||||
anchors.rightMargin: Metrics.margin(16)
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
MaterialSymbol {
|
||||
icon: Volume.defaultSink.audio.muted ? "volume_off" : "volume_up"
|
||||
color: Volume.defaultSink.audio.muted ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSurfaceVariant
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: "Mute output"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledSwitch {
|
||||
checked: Volume.defaultSink.audio.muted
|
||||
onToggled: Volume.toggleMuted(Volume.defaultSink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(20)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
radius: Metrics.radius("large")
|
||||
color: Appearance.m3colors.m3secondaryContainer
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
icon: "mic"
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Input"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: Volume.sources.length > 0
|
||||
text: Volume.defaultSource.description
|
||||
font.pixelSize: Metrics.fontSize(13)
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: Volume.sources.length === 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 120
|
||||
radius: Metrics.radius("small")
|
||||
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
MaterialSymbol {
|
||||
icon: "mic_off"
|
||||
iconSize: Metrics.iconSize(48)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.3)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "No input devices"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: Volume.sources.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
opacity: 0.4
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Volume.sources.length > 0
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
text: "Volume"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledText {
|
||||
animate: false
|
||||
text: Math.round(Volume.defaultSource.audio.volume * 100) + "%"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit SemiBold")
|
||||
color: Appearance.m3colors.m3primary
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(16)
|
||||
|
||||
MaterialSymbol {
|
||||
icon: Volume.defaultSource.audio.muted ? "mic_off" : "mic"
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
|
||||
StyledSlider {
|
||||
id: inputVolumeSlider
|
||||
Layout.fillWidth: true
|
||||
value: Volume.defaultSource.audio.volume * 100
|
||||
onValueChanged: Volume.setSourceVolume(value / 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Volume.sources.length > 0
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
StyledText {
|
||||
text: "Device"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.family: Metrics.fontFamily("Outfit Medium")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledDropDown {
|
||||
Layout.fillWidth: true
|
||||
label: "Input device"
|
||||
model: Volume.sources.map(d => d.description)
|
||||
currentIndex: {
|
||||
for (let i = 0; i < Volume.sources.length; i++)
|
||||
if (Volume.sources[i].name === Volume.defaultSource.name) return i
|
||||
return -1
|
||||
}
|
||||
onSelectedIndexChanged: index => {
|
||||
if (index >= 0 && index < Volume.sources.length)
|
||||
Volume.setDefaultSource(Volume.sources[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: Volume.sources.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 56
|
||||
radius: Metrics.radius("small")
|
||||
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Metrics.margin(16)
|
||||
anchors.rightMargin: Metrics.margin(16)
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
MaterialSymbol {
|
||||
icon: Volume.defaultSource.audio.muted ? "mic_off" : "mic"
|
||||
color: Volume.defaultSource.audio.muted ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSurfaceVariant
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: "Mute input"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledSwitch {
|
||||
checked: Volume.defaultSource.audio.muted
|
||||
onToggled: Volume.toggleMuted(Volume.defaultSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
property string barKey: "bar"
|
||||
title: "Bar"
|
||||
description: "Adjust the bar's look."
|
||||
|
||||
ContentCard {
|
||||
id: monitorSelectorCard
|
||||
|
||||
StyledText {
|
||||
text: "Monitor Bar Configuration"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (Config.runtime.monitors?.[monitorSelector.model[monitorSelector.currentIndex]]?.bar)
|
||||
? "This monitor has its own bar configuration."
|
||||
: "This monitor currently uses the global bar."
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.spacing("normal")
|
||||
|
||||
StyledDropDown {
|
||||
id: monitorSelector
|
||||
Layout.preferredWidth: 220
|
||||
model: Xrandr.monitors.map(m => m.name)
|
||||
currentIndex: 0
|
||||
onCurrentIndexChanged: monitorSelectorCard.updateMonitorProperties()
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledButton {
|
||||
id: createButton
|
||||
icon: "add"
|
||||
text: "Override Bar: (" + monitorSelector.model[monitorSelector.currentIndex] + ")"
|
||||
Layout.preferredWidth: 280
|
||||
onClicked: {
|
||||
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
|
||||
if (!monitorName) return
|
||||
if (!Config.runtime.monitors) Config.runtime.monitors = {}
|
||||
if (!Config.runtime.monitors[monitorName])
|
||||
Config.runtime.monitors[monitorName] = {}
|
||||
|
||||
const defaultBar = {
|
||||
density: 50,
|
||||
enabled: true,
|
||||
floating: false,
|
||||
gothCorners: true,
|
||||
margins: 16,
|
||||
merged: false,
|
||||
modules: {
|
||||
height: 34,
|
||||
paddingColor: "#1f1f1f",
|
||||
radius: 17,
|
||||
statusIcons: {
|
||||
bluetoothStatusEnabled: true,
|
||||
enabled: true,
|
||||
networkStatusEnabled: true
|
||||
},
|
||||
systemUsage: {
|
||||
cpuStatsEnabled: true,
|
||||
enabled: true,
|
||||
memoryStatsEnabled: true,
|
||||
tempStatsEnabled: true
|
||||
},
|
||||
workspaces: {
|
||||
enabled: true,
|
||||
showAppIcons: true,
|
||||
showJapaneseNumbers: false,
|
||||
workspaceIndicators: 8
|
||||
}
|
||||
},
|
||||
position: "top",
|
||||
radius: 23
|
||||
}
|
||||
|
||||
Config.updateKey("monitors." + monitorName + ".bar", defaultBar)
|
||||
monitorSelectorCard.updateMonitorProperties()
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
id: deleteButton
|
||||
icon: "delete"
|
||||
text: "Use Global Bar: (" + monitorSelector.model[monitorSelector.currentIndex] + ")"
|
||||
secondary: true
|
||||
Layout.preferredWidth: 280
|
||||
onClicked: {
|
||||
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
|
||||
if (!monitorName) return
|
||||
Config.updateKey("monitors." + monitorName + ".bar", undefined)
|
||||
monitorSelectorCard.updateMonitorProperties()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateMonitorProperties() {
|
||||
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
|
||||
const monitorBar = Config.runtime.monitors?.[monitorName]?.bar
|
||||
barKey = monitorBar ? "monitors." + monitorName + ".bar" : "bar"
|
||||
|
||||
createButton.enabled = !monitorBar
|
||||
deleteButton.enabled = !!monitorBar
|
||||
|
||||
monitorSelector.model = Xrandr.monitors.map(m => m.name)
|
||||
monitorSelector.currentIndex = Xrandr.monitors.findIndex(m => m.name === monitorName)
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Bar"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Position"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.spacing(8)
|
||||
Repeater {
|
||||
model: ["Top", "Bottom", "Left", "Right"]
|
||||
delegate: StyledButton {
|
||||
property string pos: modelData.toLowerCase()
|
||||
text: modelData
|
||||
Layout.fillWidth: true
|
||||
checked: ConfigResolver.bar(monitorSelector.model[monitorSelector.currentIndex]).position === pos
|
||||
topLeftRadius: Metrics.radius("normal")
|
||||
topRightRadius: Metrics.radius("normal")
|
||||
bottomLeftRadius: Metrics.radius("normal")
|
||||
bottomRightRadius: Metrics.radius("normal")
|
||||
onClicked: Config.updateKey(barKey + ".position", pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Toggle the bar visibility on/off"
|
||||
prefField: barKey + ".enabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Floating Bar"
|
||||
description: "Make the bar float above other windows instead of being part of the desktop"
|
||||
prefField: barKey + ".floating"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Goth Corners"
|
||||
description: "Apply gothic-style corner cutouts to the bar"
|
||||
prefField: barKey + ".gothCorners"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Merged Layout"
|
||||
description: "Merge all modules into a single continuous layout"
|
||||
prefField: barKey + ".merged"
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Bar Rounding & Size"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
NumberStepper {
|
||||
label: "Bar Density"
|
||||
prefField: barKey + ".density"
|
||||
description: "Modify the bar's density"
|
||||
minimum: 40
|
||||
maximum: 128
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Bar Radius"
|
||||
prefField: barKey + ".radius"
|
||||
description: "Modify the bar's radius"
|
||||
minimum: 10
|
||||
maximum: 128
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Module Container Radius"
|
||||
prefField: barKey + ".modules.radius"
|
||||
description: "Modify the bar's module.radius"
|
||||
minimum: 10
|
||||
maximum: 128
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Module Height"
|
||||
prefField: barKey + ".modules.height"
|
||||
description: "Modify the bar's module.height"
|
||||
minimum: 10
|
||||
maximum: 128
|
||||
}
|
||||
NumberStepper {
|
||||
label: "Workspace Indicators"
|
||||
prefField: barKey + ".modules.workspaces.workspaceIndicators"
|
||||
description: "Adjust how many workspace indicators to show."
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Bar Modules"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Workspaces"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Show workspace indicator module"
|
||||
prefField: barKey + ".modules.workspaces.enabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show App Icons"
|
||||
description: "Display application icons in workspace indicators"
|
||||
prefField: barKey + ".modules.workspaces.showAppIcons"
|
||||
enabled: !barKey.modules.workspaces.showJapaneseNumbers && Compositor.require("hyprland")
|
||||
opacity: !barKey.modules.workspaces.showJapaneseNumbers && Compositor.require("hyprland") ? 1 : 0.8
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Japanese Numbers"
|
||||
description: "Use Japanese-style numbers instead of standard numerals"
|
||||
prefField: barKey + ".modules.workspaces.showJapaneseNumbers"
|
||||
enabled: !barKey.modules.workspaces.showAppIcons
|
||||
opacity: !barKey.modules.workspaces.showAppIcons ? 1 : 0.8
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Status Icons"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Show status icons module (wifi, bluetooth)"
|
||||
prefField: barKey + ".modules.statusIcons.enabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Wifi Status"
|
||||
description: "Display wifi connection status and signal strength"
|
||||
prefField: barKey + ".modules.statusIcons.networkStatusEnabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Bluetooth Status"
|
||||
description: "Display bluetooth connection status"
|
||||
prefField: barKey + ".modules.statusIcons.bluetoothStatusEnabled"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "System Stats"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Show system resource monitoring module"
|
||||
prefField: barKey + ".modules.systemUsage.enabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Cpu Usage Stats"
|
||||
description: "Display CPU usage percentage and load"
|
||||
prefField: barKey + ".modules.systemUsage.cpuStatsEnabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Memory Usage Stats"
|
||||
description: "Display RAM usage and available memory"
|
||||
prefField: barKey + ".modules.systemUsage.memoryStatsEnabled"
|
||||
}
|
||||
StyledSwitchOption {
|
||||
title: "Show Cpu Temperature Stats"
|
||||
description: "Display CPU temperature readings"
|
||||
prefField: barKey + ".modules.systemUsage.tempStatsEnabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
import Quickshell.Bluetooth as QsBluetooth
|
||||
|
||||
ContentMenu {
|
||||
title: "Bluetooth"
|
||||
description: "Manage Bluetooth devices and connections."
|
||||
|
||||
ContentCard {
|
||||
ContentRowCard {
|
||||
cardSpacing: Metrics.spacing(0)
|
||||
verticalPadding: Bluetooth.defaultAdapter.enabled ? Metrics.padding(10) : Metrics.padding(0)
|
||||
cardMargin: Metrics.margin(0)
|
||||
|
||||
StyledText {
|
||||
text: powerSwitch.checked ? "Power: On" : "Power: Off"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledSwitch {
|
||||
id: powerSwitch
|
||||
checked: Bluetooth.defaultAdapter?.enabled
|
||||
onToggled: Bluetooth.defaultAdapter.enabled = checked
|
||||
}
|
||||
}
|
||||
|
||||
ContentRowCard {
|
||||
visible: Bluetooth.defaultAdapter.enabled
|
||||
cardSpacing: Metrics.spacing(0)
|
||||
verticalPadding: Metrics.padding(10)
|
||||
cardMargin: Metrics.margin(0)
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Discoverable"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Allow other devices to find this computer."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledSwitch {
|
||||
checked: Bluetooth.defaultAdapter?.discoverable
|
||||
onToggled: Bluetooth.defaultAdapter.discoverable = checked
|
||||
}
|
||||
}
|
||||
|
||||
ContentRowCard {
|
||||
visible: Bluetooth.defaultAdapter.enabled
|
||||
cardSpacing: Metrics.spacing(0)
|
||||
verticalPadding: Metrics.padding(0)
|
||||
cardMargin: Metrics.margin(0)
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Scanning"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Search for nearby Bluetooth devices."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledSwitch {
|
||||
checked: Bluetooth.defaultAdapter?.discovering
|
||||
onToggled: Bluetooth.defaultAdapter.discovering = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: connectedDevices.count > 0
|
||||
|
||||
StyledText {
|
||||
text: "Connected Devices"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: connectedDevices
|
||||
model: Bluetooth.devices.filter(d => d.connected)
|
||||
|
||||
delegate: BluetoothDeviceCard {
|
||||
device: modelData
|
||||
statusText: modelData.batteryAvailable
|
||||
? "Connected, " + Math.floor(modelData.battery * 100) + "% left"
|
||||
: "Connected"
|
||||
showDisconnect: true
|
||||
showRemove: true
|
||||
usePrimary: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Bluetooth.defaultAdapter?.enabled
|
||||
|
||||
StyledText {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: pairedDevices.count === 0
|
||||
width: parent.width
|
||||
height: Metrics.spacing(40)
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
text: "No paired devices"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: pairedDevices
|
||||
model: Bluetooth.devices.filter(d => !d.connected && d.paired)
|
||||
|
||||
delegate: BluetoothDeviceCard {
|
||||
device: modelData
|
||||
statusText: "Not connected"
|
||||
showConnect: true
|
||||
showRemove: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Bluetooth.defaultAdapter?.enabled
|
||||
|
||||
StyledText {
|
||||
text: "Available Devices"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: discoveredDevices.count === 0 && !Bluetooth.defaultAdapter.discovering
|
||||
width: parent.width
|
||||
height: Metrics.spacing(40)
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "No new devices found"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: discoveredDevices
|
||||
model: Bluetooth.devices.filter(d => !d.paired && !d.connected)
|
||||
|
||||
delegate: BluetoothDeviceCard {
|
||||
device: modelData
|
||||
statusText: "Discovered"
|
||||
showConnect: true
|
||||
showPair: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.components
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import Quickshell.Bluetooth as QsBluetooth
|
||||
|
||||
ContentRowCard {
|
||||
id: deviceRow
|
||||
property var device
|
||||
property string statusText: ""
|
||||
property bool usePrimary: false
|
||||
property bool showConnect: false
|
||||
property bool showDisconnect: false
|
||||
property bool showPair: false
|
||||
property bool showRemove: false
|
||||
|
||||
cardMargin: Metrics.margin(0)
|
||||
cardSpacing: Metrics.spacing(10)
|
||||
verticalPadding: Metrics.padding(0)
|
||||
opacity: device.state === QsBluetooth.BluetoothDeviceState.Connecting ||
|
||||
device.state === QsBluetooth.BluetoothDeviceState.Disconnecting ? 0.6 : 1
|
||||
|
||||
function mapBluetoothIcon(dbusIcon, name) {
|
||||
console.log(dbusIcon, " / ", name)
|
||||
const iconMap = {
|
||||
"audio-headset": "headset",
|
||||
"audio-headphones": "headphones",
|
||||
"input-keyboard": "keyboard",
|
||||
"input-mouse": "mouse",
|
||||
"input-gaming": "sports_esports",
|
||||
"phone": "phone_android",
|
||||
"computer": "computer",
|
||||
"printer": "print",
|
||||
"camera": "photo_camera",
|
||||
"unknown": "bluetooth"
|
||||
}
|
||||
return iconMap[dbusIcon] || "bluetooth"
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
icon: mapBluetoothIcon(device.icon, device.name)
|
||||
font.pixelSize: Metrics.fontSize(32)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Metrics.spacing(0)
|
||||
|
||||
StyledText {
|
||||
text: device.name || device.address
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: statusText
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: usePrimary
|
||||
? Appearance.m3colors.m3primary
|
||||
: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledButton {
|
||||
visible: showConnect
|
||||
icon: "link"
|
||||
onClicked: device.connect()
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
visible: showDisconnect
|
||||
icon: "link_off"
|
||||
onClicked: device.disconnect()
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
visible: showPair
|
||||
icon: "add"
|
||||
onClicked: device.pair()
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
visible: showRemove
|
||||
icon: "delete"
|
||||
onClicked: Bluetooth.removeDevice(device)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
title: "Launcher"
|
||||
description: "Adjust launcher's settings."
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Filters & Search"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Fuzzy Search"
|
||||
description: "Enable or disable fuzzy search."
|
||||
prefField: "launcher.fuzzySearchEnabled"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: webEngineSelector
|
||||
|
||||
property string title: "Web Search Engine"
|
||||
property string description: "Choose the web search engine for web searches."
|
||||
property string prefField: ''
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: webEngineSelector.title
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: webEngineSelector.description
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledDropDown {
|
||||
label: "Engine"
|
||||
model: ["Google", "Brave", "DuckDuckGo", "Bing"]
|
||||
// Set the initial index based on the lowercase value in Config
|
||||
currentIndex: {
|
||||
switch (Config.runtime.launcher.webSearchEngine.toLowerCase()) {
|
||||
case "google":
|
||||
return 0;
|
||||
case "brave":
|
||||
return 1;
|
||||
case "duckduckgo":
|
||||
return 2;
|
||||
case "bing":
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
onSelectedIndexChanged: (index) => {
|
||||
// Update Config with lowercase version of selected model
|
||||
Config.updateKey("launcher.webSearchEngine", model[index].toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
title: "Miscellaneous"
|
||||
description: "Configure misc settings."
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Versions"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: releaseChannelSelector
|
||||
|
||||
property string title: "Release Channel"
|
||||
property string description: "Choose the release channel for updates."
|
||||
property string prefField: ''
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: releaseChannelSelector.title
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: releaseChannelSelector.description
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledDropDown {
|
||||
label: "Type"
|
||||
model: ["Stable", "Edge (indev)"]
|
||||
currentIndex: Config.runtime.shell.releaseChannel === "edge" ? 1 : 0
|
||||
onSelectedIndexChanged: (index) => {
|
||||
Config.updateKey("shell.releaseChannel", index === 1 ? "edge" : "stable");
|
||||
UpdateNotifier.notified = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Intelligence"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Enable or disable intelligence."
|
||||
prefField: "misc.intelligence.enabled"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Intelligence Bearer/API"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: apiKeyTextField
|
||||
|
||||
clip: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
placeholderText: Config.runtime.misc.intelligence.apiKey !== "" ? Config.runtime.misc.intelligence.apiKey : "Bearer Key"
|
||||
Layout.fillWidth: true
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_S && (event.modifiers & Qt.ControlModifier)) {
|
||||
event.accepted = true;
|
||||
Config.updateKey("misc.intelligence.apiKey", apiKeyTextField.text);
|
||||
Quickshell.execDetached(["notify-send", "Saved Bearer/API Key"])
|
||||
}
|
||||
}
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 20
|
||||
}
|
||||
|
||||
InfoCard {
|
||||
title: "How to save the api key"
|
||||
description: "In order to save the api key press Ctrl+S and it will save the api key to the config."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
import qs.modules.functions
|
||||
|
||||
Item {
|
||||
id: networkRow
|
||||
property var connection
|
||||
property bool isActive: false
|
||||
property bool showConnect: false
|
||||
property bool showDisconnect: false
|
||||
property bool showPasswordField: false
|
||||
property string password: ""
|
||||
|
||||
width: parent.width
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
|
||||
function signalIcon(strength, secure) {
|
||||
if (!connection) return "network_wifi";
|
||||
if (connection.type === "ethernet") return "settings_ethernet";
|
||||
if (strength >= 75) return "network_wifi";
|
||||
if (strength >= 50) return "network_wifi_3_bar";
|
||||
if (strength >= 25) return "network_wifi_2_bar";
|
||||
if (strength > 0) return "network_wifi_1_bar";
|
||||
return "network_wifi_1_bar";
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
// Signal icon with lock overlay
|
||||
Item {
|
||||
width: Metrics.spacing(32)
|
||||
height: Metrics.spacing(32)
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.fill: parent
|
||||
icon: connection ? signalIcon(connection.strength, connection.isSecure) : "network_wifi"
|
||||
font.pixelSize: Metrics.fontSize(32)
|
||||
}
|
||||
|
||||
// Lock overlay (anchors are safe because Item is not layout-managed)
|
||||
MaterialSymbol {
|
||||
icon: "lock"
|
||||
visible: connection && connection.type === "wifi" && connection.isSecure
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Metrics.spacing(0)
|
||||
|
||||
StyledText {
|
||||
text: connection ? connection.name : ""
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: connection ? (
|
||||
isActive ? "Connected" :
|
||||
connection.type === "ethernet" ? connection.device || "Ethernet" :
|
||||
connection.isSecure ? "Secured" : "Open"
|
||||
) : ""
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: isActive
|
||||
? Appearance.m3colors.m3primary
|
||||
: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledButton {
|
||||
visible: showConnect && !showPasswordField
|
||||
icon: "link"
|
||||
onClicked: {
|
||||
if (!connection) return;
|
||||
if (connection.type === "ethernet") Network.connect(connection, "")
|
||||
else if (connection.isSecure) showPasswordField = true
|
||||
else Network.connect(connection, "")
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
visible: showDisconnect && !showPasswordField
|
||||
icon: "link_off"
|
||||
onClicked: Network.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// Password row
|
||||
RowLayout {
|
||||
visible: showPasswordField && connection && connection.type === "wifi"
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
StyledTextField {
|
||||
padding: Metrics.padding(10)
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Enter password"
|
||||
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
|
||||
onTextChanged: networkRow.password = text
|
||||
onAccepted: {
|
||||
if (!connection) return;
|
||||
Network.connect(connection, networkRow.password)
|
||||
showPasswordField = false
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
property bool showPassword: false
|
||||
icon: parent.showPassword ? "visibility" : "visibility_off"
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "link"
|
||||
onClicked: {
|
||||
if (!connection) return;
|
||||
Network.connect(connection, networkRow.password)
|
||||
showPasswordField = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.functions
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
title: "Network"
|
||||
description: "Manage network connections."
|
||||
|
||||
ContentCard {
|
||||
ContentRowCard {
|
||||
cardSpacing: Metrics.spacing(0)
|
||||
verticalPadding: Network.wifiEnabled ? Metrics.padding(10) : Metrics.padding(0)
|
||||
cardMargin: Metrics.margin(0)
|
||||
|
||||
StyledText {
|
||||
text: powerSwitch.checked ? "Wi-Fi: On" : "Wi-Fi: Off"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledSwitch {
|
||||
id: powerSwitch
|
||||
checked: Network.wifiEnabled
|
||||
onToggled: Network.enableWifi(checked)
|
||||
}
|
||||
}
|
||||
|
||||
ContentRowCard {
|
||||
visible: Network.wifiEnabled
|
||||
cardSpacing: Metrics.spacing(0)
|
||||
verticalPadding: Metrics.padding(10)
|
||||
cardMargin: Metrics.margin(0)
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Scanning"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Search for nearby Wi-Fi networks."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: ColorUtils.transparentize(
|
||||
Appearance.m3colors.m3onSurface, 0.4
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledSwitch {
|
||||
checked: Network.scanning
|
||||
onToggled: {
|
||||
if (checked)
|
||||
Network.rescan()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfoCard {
|
||||
visible: Network.message !== "" && Network.message !== "ok"
|
||||
icon: "error"
|
||||
backgroundColor: Appearance.m3colors.m3error
|
||||
contentColor: Appearance.m3colors.m3onError
|
||||
title: "Failed to connect to " + Network.lastNetworkAttempt
|
||||
description: Network.message
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Network.active !== null
|
||||
|
||||
StyledText {
|
||||
text: "Active Connection"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
NetworkCard {
|
||||
connection: Network.active
|
||||
isActive: true
|
||||
showDisconnect: Network.active?.type === "wifi"
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Network.connections.filter(c => c.type === "ethernet").length > 0
|
||||
|
||||
StyledText {
|
||||
text: "Ethernet"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Network.connections.filter(c => c.type === "ethernet" && !c.active)
|
||||
delegate: NetworkCard {
|
||||
connection: modelData
|
||||
showConnect: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Network.wifiEnabled
|
||||
|
||||
StyledText {
|
||||
text: "Available Wi-Fi Networks"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: Network.connections.filter(c => c.type === "wifi").length === 0 && !Network.scanning
|
||||
width: parent.width
|
||||
height: Metrics.spacing(40)
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
text: "No networks found"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.4)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Network.connections.filter(c => c.type === "wifi" && !c.active)
|
||||
delegate: NetworkCard {
|
||||
connection: modelData
|
||||
showConnect: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
visible: Network.savedNetworks.length > 0
|
||||
StyledText {
|
||||
text: "Remembered Networks"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: Network.savedNetworks.length === 0
|
||||
width: parent.width
|
||||
height: Metrics.spacing(40)
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
text: "No remembered networks"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Network.connections.filter(c => c.type === "wifi" && c.saved && !c.active)
|
||||
delegate: NetworkCard {
|
||||
connection: modelData
|
||||
showConnect: false
|
||||
showDisconnect: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
import qs.modules.functions
|
||||
|
||||
ContentMenu {
|
||||
title: "Notifications & Overlays"
|
||||
description: "Adjust notification and overlay settings."
|
||||
|
||||
function indexFromPosition(pos, model) {
|
||||
pos = pos.toLowerCase()
|
||||
for (let i = 0; i < model.length; i++) {
|
||||
if (model[i].toLowerCase().replace(" ", "-") === pos)
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
|
||||
StyledText {
|
||||
text: "Notifications"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Enable or disable built-in notification daemon."
|
||||
prefField: "notifications.enabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Do not disturb enabled"
|
||||
description: "Enable or disable dnd."
|
||||
prefField: "notifications.doNotDisturb"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Notification Position"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Select where notification will be shown."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
id: notificationDropdown
|
||||
label: "Position"
|
||||
|
||||
property var positions: ["Top Left", "Top Right", "Top"]
|
||||
|
||||
model: positions
|
||||
|
||||
currentIndex:
|
||||
indexFromPosition(
|
||||
Config.runtime.notifications.position,
|
||||
positions
|
||||
)
|
||||
|
||||
onSelectedIndexChanged: function(index) {
|
||||
Config.updateKey(
|
||||
"notifications.position",
|
||||
positions[index].toLowerCase().replace(" ", "-")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Test Notifications"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Run a test notification."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledButton {
|
||||
text: "Test"
|
||||
icon: "chat"
|
||||
|
||||
onClicked:
|
||||
Quickshell.execDetached([
|
||||
"notify-send",
|
||||
"Quickshell",
|
||||
"This is a test notification"
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
|
||||
StyledText {
|
||||
text: "Overlays / OSDs"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Enable or disable built-in osd daemon."
|
||||
prefField: "overlays.enabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Volume OSD enabled"
|
||||
description: "Enable or disable volume osd."
|
||||
prefField: "overlays.volumeOverlayEnabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Brightness OSD enabled"
|
||||
description: "Enable or disable brightness osd."
|
||||
prefField: "overlays.brightnessOverlayEnabled"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Brightness OSD Position"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Choose where brightness OSD is shown."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
|
||||
property var positions:
|
||||
["Top Left","Top Right","Bottom Left","Bottom Right","Top","Bottom"]
|
||||
|
||||
model: positions
|
||||
|
||||
currentIndex:
|
||||
indexFromPosition(
|
||||
Config.runtime.overlays.brightnessOverlayPosition,
|
||||
positions
|
||||
)
|
||||
|
||||
onSelectedIndexChanged: function(index) {
|
||||
Config.updateKey(
|
||||
"overlays.brightnessOverlayPosition",
|
||||
positions[index].toLowerCase().replace(" ", "-")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: "Volume OSD Position"
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Choose where volume OSD is shown."
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
|
||||
property var positions:
|
||||
["Top Left","Top Right","Bottom Left","Bottom Right","Top","Bottom"]
|
||||
|
||||
model: positions
|
||||
|
||||
currentIndex:
|
||||
indexFromPosition(
|
||||
Config.runtime.overlays.volumeOverlayPosition,
|
||||
positions
|
||||
)
|
||||
|
||||
onSelectedIndexChanged: function(index) {
|
||||
Config.updateKey(
|
||||
"overlays.volumeOverlayPosition",
|
||||
positions[index].toLowerCase().replace(" ", "-")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.plugins
|
||||
|
||||
ContentMenu {
|
||||
title: "Plugins"
|
||||
description: "Modify and Customize Installed Plugins."
|
||||
|
||||
ContentCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
color: "transparent"
|
||||
|
||||
GridLayout {
|
||||
id: grid
|
||||
columns: 1
|
||||
Layout.fillWidth: true
|
||||
columnSpacing: Metrics.spacing(16)
|
||||
rowSpacing: Metrics.spacing(16)
|
||||
|
||||
StyledText {
|
||||
text: "Plugins not found!"
|
||||
font.pixelSize: Metrics.fontSize(20)
|
||||
font.bold: true
|
||||
visible: PluginLoader.plugins.length === 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: PluginLoader.plugins
|
||||
|
||||
delegate: ContentCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
asynchronous: true
|
||||
source: Qt.resolvedUrl(
|
||||
Directories.shellConfig + "/plugins/" + modelData + "/Settings.qml"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Scope {
|
||||
property var settingsWindow: null
|
||||
|
||||
IpcHandler {
|
||||
function open(menu: string) {
|
||||
Globals.states.settingsOpen = true;
|
||||
|
||||
if (menu !== "" && settingsWindow !== null) {
|
||||
for (var i = 0; i < settingsWindow.menuModel.length; i++) {
|
||||
var item = settingsWindow.menuModel[i];
|
||||
if (!item.header && item.label.toLowerCase() === menu.toLowerCase()) {
|
||||
settingsWindow.selectedIndex = item.page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
target: "settings"
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
active: Globals.states.settingsOpen
|
||||
|
||||
Window {
|
||||
id: root
|
||||
width: 1280
|
||||
height: 720
|
||||
visible: true
|
||||
title: "Nucleus - Settings"
|
||||
color: Appearance.m3colors.m3background
|
||||
onClosing: Globals.states.settingsOpen = false
|
||||
Component.onCompleted: settingsWindow = root
|
||||
|
||||
property int selectedIndex: 0
|
||||
property bool sidebarCollapsed: false
|
||||
|
||||
property var menuModel: [
|
||||
{ "header": true, "label": "System" },
|
||||
{ "icon": "bluetooth", "label": "Bluetooth", "page": 0 },
|
||||
{ "icon": "network_wifi", "label": "Network", "page": 1 },
|
||||
{ "icon": "volume_up", "label": "Audio", "page": 2 },
|
||||
{ "icon": "instant_mix", "label": "Appearance", "page": 3 },
|
||||
|
||||
{ "header": true, "label": "Customization" },
|
||||
{ "icon": "toolbar", "label": "Bar", "page": 4 },
|
||||
{ "icon": "wallpaper", "label": "Wallpapers", "page": 5 },
|
||||
{ "icon": "apps", "label": "Launcher", "page": 6 },
|
||||
{ "icon": "chat", "label": "Notifications", "page": 7 },
|
||||
{ "icon": "extension", "label": "Plugins", "page": 8 },
|
||||
{ "icon": "apps", "label": "Store", "page": 9 },
|
||||
{ "icon": "build", "label": "Miscellaneous", "page": 10 },
|
||||
|
||||
{ "header": true, "label": "About" },
|
||||
{ "icon": "info", "label": "About", "page": 11 }
|
||||
]
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: sidebarBG
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: root.sidebarCollapsed ? 80 : 350
|
||||
color: Appearance.m3colors.m3surfaceContainerLow
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(40)
|
||||
spacing: Metrics.spacing(5)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: "Settings"
|
||||
font.family: "Outfit ExtraBold"
|
||||
font.pixelSize: Metrics.fontSize(28)
|
||||
visible: !root.sidebarCollapsed
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
Layout.preferredHeight: 40
|
||||
icon: root.sidebarCollapsed ? "chevron_right" : "chevron_left"
|
||||
secondary: true
|
||||
onClicked: root.sidebarCollapsed = !root.sidebarCollapsed
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: sidebarList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: root.menuModel
|
||||
spacing: Metrics.spacing(5)
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
width: sidebarList.width
|
||||
height: modelData.header ? (root.sidebarCollapsed ? 0 : 30) : 42
|
||||
visible: !modelData.header || !root.sidebarCollapsed
|
||||
|
||||
// header
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
StyledText {
|
||||
y: (parent.height - height) * 0.5
|
||||
x: 10
|
||||
text: modelData.label
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.bold: true
|
||||
opacity: modelData.header ? 1 : 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !modelData.header
|
||||
radius: Appearance.rounding.large
|
||||
color: root.selectedIndex === modelData.page
|
||||
? Appearance.m3colors.m3primary
|
||||
: "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
spacing: 10
|
||||
|
||||
MaterialSymbol {
|
||||
visible: !modelData.header
|
||||
icon: modelData.icon ? modelData.icon : ""
|
||||
iconSize: Metrics.iconSize(24)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
visible: !root.sidebarCollapsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: modelData.page !== undefined
|
||||
onClicked: {
|
||||
root.selectedIndex = modelData.page
|
||||
settingsStack.currentIndex = modelData.page
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 180; easing.type: Easing.InOutCubic }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StackLayout {
|
||||
id: settingsStack
|
||||
anchors.left: sidebarBG.right
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
currentIndex: root.selectedIndex
|
||||
|
||||
BluetoothConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
NetworkConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
AudioConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
AppearanceConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
BarConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
WallpaperConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
LauncherConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
NotificationConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Plugins { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Store { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
MiscConfig { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
About { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.plugins
|
||||
|
||||
ContentMenu {
|
||||
title: "Store"
|
||||
description: "Manage plugins and other stuff for the shell."
|
||||
|
||||
ContentCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
GridLayout {
|
||||
columns: 1
|
||||
Layout.fillWidth: true
|
||||
columnSpacing: Metrics.spacing(16)
|
||||
rowSpacing: Metrics.spacing(16)
|
||||
|
||||
Repeater {
|
||||
model: PluginParser.model
|
||||
|
||||
delegate: StyledRect {
|
||||
Layout.preferredHeight: 90
|
||||
Layout.fillWidth: true
|
||||
radius: Metrics.radius("small")
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("normal")
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize("large")
|
||||
text: name
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.spacing(6)
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
text: author
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
text: "| Requires Nucleus " + requires_nucleus
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
text: description
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
StyledButton {
|
||||
icon: "download"
|
||||
text: "Install"
|
||||
visible: !installed
|
||||
secondary: true
|
||||
Layout.preferredWidth: 140
|
||||
onClicked: PluginParser.install(id)
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "update"
|
||||
text: "Update"
|
||||
visible: installed
|
||||
secondary: true
|
||||
Layout.preferredWidth: 140
|
||||
onClicked: PluginParser.update(id)
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "delete"
|
||||
text: "Remove"
|
||||
visible: installed
|
||||
secondary: true
|
||||
Layout.preferredWidth: 140
|
||||
onClicked: PluginParser.uninstall(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
ContentMenu {
|
||||
property string displayName: root.screen?.name ?? ""
|
||||
property var intervalOptions: [{
|
||||
"value": 5,
|
||||
"label": "5 minutes"
|
||||
}, {
|
||||
"value": 15,
|
||||
"label": "15 minutes"
|
||||
}, {
|
||||
"value": 30,
|
||||
"label": "30 minutes"
|
||||
}, {
|
||||
"value": 60,
|
||||
"label": "1 hour"
|
||||
}, {
|
||||
"value": 120,
|
||||
"label": "2 hours"
|
||||
}, {
|
||||
"value": 360,
|
||||
"label": "6 hours"
|
||||
}]
|
||||
|
||||
function getIntervalIndex(minutes) {
|
||||
for (let i = 0; i < intervalOptions.length; i++) {
|
||||
if (intervalOptions[i].value === minutes)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
title: "Wallpaper"
|
||||
description: "Manage your wallpapers"
|
||||
|
||||
ContentCard {
|
||||
ClippingRectangle {
|
||||
id: wpContainer
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: root.screen.width / 2
|
||||
height: width * root.screen.height / root.screen.width
|
||||
radius: Metrics.radius("unsharpenmore")
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
|
||||
StyledText {
|
||||
text: "Current Wallpaper:"
|
||||
font.pixelSize: Metrics.fontSize("big")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
id: wpPreview
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignCenter
|
||||
anchors.fill: parent
|
||||
radius: Metrics.radius("unsharpenmore")
|
||||
color: Appearance.m3colors.m3paddingContainer
|
||||
layer.enabled: true
|
||||
|
||||
StyledText {
|
||||
opacity: !Config.runtime.appearance.background.enabled ? 1 : 0
|
||||
font.pixelSize: Metrics.fontSize("title")
|
||||
text: "Wallpaper Manager Disabled"
|
||||
anchors.centerIn: parent
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: Config.runtime.appearance.animations.enabled
|
||||
Anim { }
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
opacity: Config.runtime.appearance.background.enabled ? 1 : 0
|
||||
anchors.fill: parent
|
||||
source: previewImg + "?t=" + Date.now()
|
||||
property string previewImg: {
|
||||
const displays = Config.runtime.monitors
|
||||
const fallback = Config.runtime.appearance.background.defaultPath
|
||||
|
||||
if (!displays)
|
||||
return fallback
|
||||
|
||||
const monitor = displays?.[displayName]
|
||||
return monitor?.wallpaper ?? fallback
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: true
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: Config.runtime.appearance.animations.enabled
|
||||
Anim { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "wallpaper"
|
||||
text: "Change Wallpaper"
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["nucleus", "ipc", "call", "background", "change"]);
|
||||
}
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Enabled or disable built-in wallpaper daemon."
|
||||
prefField: "appearance.background.enabled"
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Parallax Effect"
|
||||
font.pixelSize: Metrics.fontSize("big")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled"
|
||||
description: "Enabled or disable wallpaper parallax effect."
|
||||
prefField: "appearance.background.parallax.enabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled for Sidebar Left"
|
||||
description: "Show parralax effect when sidebarLeft is opened."
|
||||
prefField: "appearance.background.parallax.enableSidebarLeft"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enabled for Sidebar Right"
|
||||
description: "Show parralax effect when sidebarRight is opened."
|
||||
prefField: "appearance.background.parallax.enableSidebarRight"
|
||||
}
|
||||
|
||||
NumberStepper {
|
||||
label: "Zoom Amount"
|
||||
description: "Adjust the zoom of the parallax effect."
|
||||
prefField: "appearance.background.parallax.zoom"
|
||||
step: 0.1
|
||||
minimum: 1.10
|
||||
maximum: 2
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard {
|
||||
StyledText {
|
||||
text: "Wallpaper Slideshow"
|
||||
font.pixelSize: Metrics.fontSize("big")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Enable Slideshow"
|
||||
description: "Automatically rotate wallpapers from a folder."
|
||||
prefField: "appearance.background.slideshow.enabled"
|
||||
}
|
||||
|
||||
StyledSwitchOption {
|
||||
title: "Include Subfolders"
|
||||
description: "Also search for wallpapers in subfolders."
|
||||
prefField: "appearance.background.slideshow.includeSubfolders"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(4)
|
||||
|
||||
StyledText {
|
||||
text: "Wallpaper Folder"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Config.runtime.appearance.background.slideshow.folder || "No folder selected"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
elide: Text.ElideMiddle
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "folder_open"
|
||||
text: "Browse"
|
||||
onClicked: folderPickerProc.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: skipWallpaper
|
||||
|
||||
property string title: "Skip To Next Wallpaper"
|
||||
property string description: "Skip to the next wallpaper in the wallpaper directory."
|
||||
property string prefField: ''
|
||||
|
||||
ColumnLayout {
|
||||
StyledText {
|
||||
text: skipWallpaper.title
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: skipWallpaper.description
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledButton {
|
||||
icon: "skip_next"
|
||||
text: "Skip Next"
|
||||
enabled: WallpaperSlideshow.wallpapers.length > 0
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["nucleus", "ipc", "call", "background", "next"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(4)
|
||||
|
||||
StyledText {
|
||||
text: "Change Interval"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "How often to change the wallpaper."
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
StyledDropDown {
|
||||
label: "Interval"
|
||||
model: intervalOptions.map((opt) => {
|
||||
return opt.label;
|
||||
})
|
||||
currentIndex: getIntervalIndex(Config.runtime.appearance.background.slideshow.interval)
|
||||
onSelectedIndexChanged: (index) => {
|
||||
Config.updateKey("appearance.background.slideshow.interval", intervalOptions[index].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: folderPickerProc
|
||||
|
||||
command: ["bash", Directories.scriptsPath + "/interface/selectfolder.sh", Config.runtime.appearance.background.slideshow.folder || Directories.pictures]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const out = text.trim();
|
||||
if (out !== "null" && out.length > 0)
|
||||
Config.updateKey("appearance.background.slideshow.folder", out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Metrics.chronoDuration(400)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.animation.curves.standard
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user