Very basic theming support

This commit is contained in:
quadbyte 2025-08-09 12:53:10 -04:00
parent b08944988d
commit 821b4e8cad
6 changed files with 309 additions and 58 deletions

View file

@ -1,28 +1,33 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Widgets
import qs.Theme
PanelWindow { PanelWindow {
id: root id: root
property var modelData property var modelData
screen: modelData screen: modelData
implicitHeight: 36 implicitHeight: 36
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
}
Item {
anchors.fill: parent
Rectangle {
anchors.fill: parent
color: Theme.backgroundPrimary
layer.enabled: true
} }
Item { // Just testing
anchors.fill: parent NoctaliaToggle {}
}
Rectangle {
anchors.fill: parent
color: "purple"
layer.enabled: true
}
}
} }

27
Services/Settings.qml Normal file
View file

@ -0,0 +1,27 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
Singleton {
id: root
property string shellName: "Noctalia"
property string settingsDir: Quickshell.env("NOCTALIA_SETTINGS_DIR")
|| (Quickshell.env("XDG_CONFIG_HOME")
|| Quickshell.env(
"HOME") + "/.config") + "/" + shellName + "/"
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE")
|| (settingsDir + "Settings.json")
property string themeFile: Quickshell.env("NOCTALIA_THEME_FILE")
|| (settingsDir + "Theme.json")
Item {
Component.onCompleted: {
// ensure settings dir
Quickshell.execDetached(["mkdir", "-p", settingsDir])
}
}
}

View file

@ -1,45 +1,46 @@
pragma Singleton pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
id: root id: root
/*
/*
Preset sizes for font, radii, ? Preset sizes for font, radii, ?
*/ */
// Font // Font
property real fontExtraLarge: 32 property real fontExtraLarge: 32
property real fontLarge: 16 property real fontLarge: 16
property real fontMedium: 14 property real fontMedium: 14
property real fontSmall: 12 property real fontSmall: 12
// Font weight // Font weight
property int fontWeightRegular: 400 property int fontWeightRegular: 400
property int fontWeightMedium: 500 property int fontWeightMedium: 500
property int fontWeightBold: 700 property int fontWeightBold: 700
// Radii // Radii
property int radiusLarge: 20 property int radiusLarge: 20
property int radiusMedium: 16 property int radiusMedium: 16
property int radiusSmall: 12 property int radiusSmall: 12
// Border // Border
property int borderThin: 1 property int borderThin: 1
property int borderMedium: 2 property int borderMedium: 2
property int borderThick: 3 property int borderThick: 3
// Spacing // Spacing
property int spacingExtraLarge: 20 property int spacingExtraLarge: 20
property int spacingLarge: 16 property int spacingLarge: 16
property int spacingMedium: 12 property int spacingMedium: 12
property int spacingSmall: 8 property int spacingSmall: 8
// Animation duration (ms)
property int animationFast: 150
property int animationNormal: 300
property int animationSlow: 500
}
// Animation duration (ms)
property int animationFast: 150
property int animationNormal: 300
property int animationSlow: 500
}

143
Theme/Theme.qml Normal file
View file

@ -0,0 +1,143 @@
// Theme.qml
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
Singleton {
id: root
// Backgrounds
property color backgroundPrimary: themeData.backgroundPrimary
property color backgroundSecondary: themeData.backgroundSecondary
property color backgroundTertiary: themeData.backgroundTertiary
// Surfaces & Elevation
property color surface: themeData.surface
property color surfaceVariant: themeData.surfaceVariant
// Text Colors
property color textPrimary: themeData.textPrimary
property color textSecondary: themeData.textSecondary
property color textDisabled: themeData.textDisabled
// Accent Colors
property color accentPrimary: themeData.accentPrimary
property color accentSecondary: themeData.accentSecondary
property color accentTertiary: themeData.accentTertiary
// Error/Warning
property color error: themeData.error
property color warning: themeData.warning
// Highlights & Focus
property color highlight: themeData.highlight
property color rippleEffect: themeData.rippleEffect
// Additional Theme Properties
property color onAccent: themeData.onAccent
property color outline: themeData.outline
// Shadows & Overlays
property color shadow: applyOpacity(themeData.shadow, "B3")
property color overlay: applyOpacity(themeData.overlay, "66")
// Font Properties
property string fontFamily: "Roboto" // Family for all text
// Design reference resolution (for scale = 1.0)
readonly property int designScreenWidth: 2560
readonly property int designScreenHeight: 1440
// Automatic, orientation-agnostic scaling
function scale(currentScreen) {
return 1.0
// // 1) Per-monitor override wins
// try {
// const overrides = Settings.settings.monitorScaleOverrides || {};
// if (currentScreen && currentScreen.name && overrides[currentScreen.name] !== undefined) {
// const overrideValue = overrides[currentScreen.name]
// if (isFinite(overrideValue)) return overrideValue
// }
// } catch (e) {
// // ignore
// }
// // 2) Fallback: scale by diagonal pixel count relative to design resolution
// try {
// const w = Math.max(1, currentScreen ? (currentScreen.width || 0) : 0)
// const h = Math.max(1, currentScreen ? (currentScreen.height || 0) : 0)
// if (w > 1 && h > 1) {
// const diag = Math.sqrt(w * w + h * h)
// const baseDiag = Math.sqrt(designScreenWidth * designScreenWidth + designScreenHeight * designScreenHeight)
// const ratio = diag / baseDiag
// // Clamp to a reasonable range for UI legibility
// return Math.max(0.9, Math.min(1.6, ratio))
// }
// } catch (e) {
// // ignore and fall through
// }
// // 3) Safe default
// return 1.0
}
function applyOpacity(color, opacity) {
return color.replace("#", "#" + opacity)
}
// FileView to load theme data from JSON file
FileView {
id: themeFile
path: Settings.themeFile
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: writeAdapter()
onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) {
// File doesn't exist, create it with default values
writeAdapter()
}
}
JsonAdapter {
id: themeData
// Backgrounds
property string backgroundPrimary: "#191724"
property string backgroundSecondary: "#1f1d2e"
property string backgroundTertiary: "#26233a"
// Surfaces & Elevation
property string surface: "#1f1d2e"
property string surfaceVariant: "#37354c"
// Text Colors
property string textPrimary: "#e0def4"
property string textSecondary: "#908caa"
property string textDisabled: "#6e6a86"
// Accent Colors
property string accentPrimary: "#ebbcba"
property string accentSecondary: "#31748f"
property string accentTertiary: "#9ccfd8"
// Error/Warning
property string error: "#eb6f92"
property string warning: "#f6c177"
// Highlights & Focus
property string highlight: "#c4a7e7"
property string rippleEffect: "#9ccfd8"
// Additional Theme Properties
property string onAccent: "#191724"
property string outline: "#44415a"
// Shadows & Overlays
property string shadow: "#191724"
property string overlay: "#191724"
}
}
}

View file

@ -0,0 +1,76 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Theme
RowLayout {
id: root
// Local scale convenience with safe fallback
readonly property real scale: (typeof screen !== 'undefined'
&& screen) ? Theme.scale(screen) : 1.0
property string label: ""
property string description: ""
property bool value: false
property var onToggled: function () {}
Layout.fillWidth: true
ColumnLayout {
spacing: 4 * scale
Layout.fillWidth: true
Text {
text: label
font.pixelSize: 13 * scale
font.bold: true
color: Theme.textPrimary
}
Text {
text: description
font.pixelSize: 12 * scale
color: Theme.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
Rectangle {
id: switcher
width: 52 * scale
height: 32 * scale
radius: height / 2
color: value ? Theme.accentPrimary : Theme.surfaceVariant
border.color: value ? Theme.accentPrimary : Theme.outline
border.width: 2 * scale
Rectangle {
width: 28 * scale
height: 28 * scale
radius: height / 2
color: Theme.surface
border.color: Theme.outline
border.width: 1 * scale
y: 2 * scale
x: value ? switcher.width - width - 2 * scale : 2 * scale
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.onToggled()
}
}
}
}

View file

@ -1,9 +1,10 @@
/* /*
Here we go, this is it. Rebuild time... Here we go, this is it. Rebuild time...
No spaghetti code, preset sizing, proper project structure No spaghetti code, preset sizing, proper project structure
only "spawn" UI, do not do anything else here. only "spawn" UI, do not do anything else here.
*/ */
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@ -11,15 +12,13 @@ import Quickshell.Widgets
import qs.Modules.Bar import qs.Modules.Bar
ShellRoot { ShellRoot {
id: root id: root
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
delegate: Bar {
modelData: item
}
delegate: Bar {
modelData: item
} }
}
} }