From 821b4e8cad8d433a826575e5749a2dc766f9d607 Mon Sep 17 00:00:00 2001 From: quadbyte Date: Sat, 9 Aug 2025 12:53:10 -0400 Subject: [PATCH] Very basic theming support --- Modules/Bar/Bar.qml | 41 ++++++----- Services/Settings.qml | 27 +++++++ Theme/Style.qml | 63 ++++++++-------- Theme/Theme.qml | 143 +++++++++++++++++++++++++++++++++++++ Widgets/NoctaliaToggle.qml | 76 ++++++++++++++++++++ shell.qml | 17 +++-- 6 files changed, 309 insertions(+), 58 deletions(-) create mode 100644 Services/Settings.qml create mode 100644 Theme/Theme.qml create mode 100644 Widgets/NoctaliaToggle.qml diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 83cd3f1..47fb6c1 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -1,28 +1,33 @@ import QtQuick import Quickshell +import qs.Widgets +import qs.Theme PanelWindow { - id: root + id: root - property var modelData + property var modelData - screen: modelData - implicitHeight: 36 - color: "transparent" + screen: modelData + implicitHeight: 36 + color: "transparent" - anchors { - top: true - left: true - right: true + anchors { + top: true + left: true + right: true + } + + Item { + anchors.fill: parent + + Rectangle { + anchors.fill: parent + color: Theme.backgroundPrimary + layer.enabled: true } - Item { - anchors.fill: parent - - Rectangle { - anchors.fill: parent - color: "purple" - layer.enabled: true - } - } + // Just testing + NoctaliaToggle {} + } } diff --git a/Services/Settings.qml b/Services/Settings.qml new file mode 100644 index 0000000..fd6bfb9 --- /dev/null +++ b/Services/Settings.qml @@ -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]) + } + } +} diff --git a/Theme/Style.qml b/Theme/Style.qml index 8bb63c1..1479346 100644 --- a/Theme/Style.qml +++ b/Theme/Style.qml @@ -1,45 +1,46 @@ pragma Singleton + import QtQuick import Quickshell import Quickshell.Io Singleton { - id: root + id: root - /* - Preset sizes for font, radii, ? + + /* + Preset sizes for font, radii, ? */ - // Font - property real fontExtraLarge: 32 - property real fontLarge: 16 - property real fontMedium: 14 - property real fontSmall: 12 + // Font + property real fontExtraLarge: 32 + property real fontLarge: 16 + property real fontMedium: 14 + property real fontSmall: 12 - // Font weight - property int fontWeightRegular: 400 - property int fontWeightMedium: 500 - property int fontWeightBold: 700 + // Font weight + property int fontWeightRegular: 400 + property int fontWeightMedium: 500 + property int fontWeightBold: 700 - // Radii - property int radiusLarge: 20 - property int radiusMedium: 16 - property int radiusSmall: 12 + // Radii + property int radiusLarge: 20 + property int radiusMedium: 16 + property int radiusSmall: 12 - // Border - property int borderThin: 1 - property int borderMedium: 2 - property int borderThick: 3 + // Border + property int borderThin: 1 + property int borderMedium: 2 + property int borderThick: 3 - // Spacing - property int spacingExtraLarge: 20 - property int spacingLarge: 16 - property int spacingMedium: 12 - property int spacingSmall: 8 - - // Animation duration (ms) - property int animationFast: 150 - property int animationNormal: 300 - property int animationSlow: 500 - } + // Spacing + property int spacingExtraLarge: 20 + property int spacingLarge: 16 + property int spacingMedium: 12 + property int spacingSmall: 8 + // Animation duration (ms) + property int animationFast: 150 + property int animationNormal: 300 + property int animationSlow: 500 +} diff --git a/Theme/Theme.qml b/Theme/Theme.qml new file mode 100644 index 0000000..93a9f2a --- /dev/null +++ b/Theme/Theme.qml @@ -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" + } + } +} diff --git a/Widgets/NoctaliaToggle.qml b/Widgets/NoctaliaToggle.qml new file mode 100644 index 0000000..26701cd --- /dev/null +++ b/Widgets/NoctaliaToggle.qml @@ -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() + } + } + } +} diff --git a/shell.qml b/shell.qml index bf70526..3ba8775 100644 --- a/shell.qml +++ b/shell.qml @@ -1,9 +1,10 @@ + + /* Here we go, this is it. Rebuild time... No spaghetti code, preset sizing, proper project structure only "spawn" UI, do not do anything else here. */ - import QtQuick import Quickshell import Quickshell.Io @@ -11,15 +12,13 @@ import Quickshell.Widgets import qs.Modules.Bar ShellRoot { - id: root + id: root - Variants { - model: Quickshell.screens - - delegate: Bar { - modelData: item - } + Variants { + model: Quickshell.screens + delegate: Bar { + modelData: item } - + } }