noctalia-shell/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml

325 lines
10 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
import Quickshell.Io
ColumnLayout {
id: root
spacing: 0
// Helper function to get color from scheme file
function getSchemeColor(schemePath, colorKey) {
// Extract scheme name from path
var schemeName = schemePath.split("/").pop().replace(".json", "")
// Try to get from cached data first
if (schemeColorsCache[schemeName] && schemeColorsCache[schemeName][colorKey]) {
return schemeColorsCache[schemeName][colorKey]
}
// Return a default color if not cached yet
return "#000000"
}
// Cache for scheme colors
property var schemeColorsCache: ({})
// Scale properties for card animations
property real cardScaleLow: 0.95
property real cardScaleHigh: 1.0
// This function is called by the FileView Repeater when a scheme file is loaded
function schemeLoaded(schemeName, jsonData) {
var colors = {}
// Extract colors from JSON data
if (jsonData && typeof jsonData === 'object') {
colors.mPrimary = jsonData.mPrimary || jsonData.primary || "#000000"
colors.mSecondary = jsonData.mSecondary || jsonData.secondary || "#000000"
colors.mTertiary = jsonData.mTertiary || jsonData.tertiary || "#000000"
colors.mError = jsonData.mError || jsonData.error || "#ff0000"
colors.mSurface = jsonData.mSurface || jsonData.surface || "#ffffff"
colors.mOnSurface = jsonData.mOnSurface || jsonData.onSurface || "#000000"
colors.mOutline = jsonData.mOutline || jsonData.outline || "#666666"
} else {
// Default colors on failure
colors = {
"mPrimary": "#000000",
"mSecondary": "#000000",
"mTertiary": "#000000",
"mError": "#ff0000",
"mSurface": "#ffffff",
"mOnSurface": "#000000",
"mOutline": "#666666"
}
}
// Update the cache. This must be done by re-assigning the whole object to trigger updates.
var newCache = schemeColorsCache
newCache[schemeName] = colors
schemeColorsCache = newCache
}
// When the list of available schemes changes, clear the cache.
// The Repeater below will automatically re-create the FileViews.
Connections {
target: ColorSchemeService
function onSchemesChanged() {
schemeColorsCache = {}
}
}
// A non-visual Item to host the Repeater that loads the color scheme files.
Item {
visible: false
id: fileLoaders
Repeater {
model: ColorSchemeService.schemes
// The delegate is a Component, which correctly wraps the non-visual FileView
delegate: Item {
FileView {
path: modelData
blockLoading: true
onLoaded: {
var schemeName = path.split("/").pop().replace(".json", "")
try {
var jsonData = JSON.parse(text())
root.schemeLoaded(schemeName, jsonData)
} catch (e) {
Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e)
root.schemeLoaded(schemeName, null) // Load defaults on parse error
}
}
}
}
}
}
// UI Code
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
padding: Style.marginMedium * scaling
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: Style.marginLarge * scaling
Layout.fillWidth: true
// Use Matugen
NToggle {
label: "Use Matugen"
description: "Automatically generate colors based on your active wallpaper using Matugen"
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
Settings.data.colorSchemes.useWallpaperColors = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginLarge * scaling
Layout.bottomMargin: Style.marginLarge * scaling
}
ColumnLayout {
spacing: Style.marginTiniest * scaling
Layout.fillWidth: true
NText {
text: "Predefined Color Schemes"
font.pointSize: Style.fontSizeLarge * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NText {
text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead."
font.pointSize: Style.fontSizeSmall * scaling
color: Color.mOnSurface
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
ColumnLayout {
spacing: Style.marginTiny * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginLarge * scaling
// Color Schemes Grid
GridLayout {
columns: 4
rowSpacing: Style.marginLarge * scaling
columnSpacing: Style.marginLarge * scaling
Layout.fillWidth: true
Repeater {
model: ColorSchemeService.schemes
Rectangle {
id: schemeCard
property string schemePath: modelData
Layout.fillWidth: true
Layout.preferredHeight: 120 * scaling
radius: Style.radiusMedium * scaling
color: getSchemeColor(modelData, "mSurface")
border.width: Math.max(1, Style.borderThick * scaling)
border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow
// Mouse area for selection
MouseArea {
anchors.fill: parent
onClicked: {
// Disable useWallpaperColors when picking a predefined color scheme
// TBC: broken uncheck useWallpaperColors
Settings.data.colorSchemes.useWallpaperColors = false
Settings.data.colorSchemes.predefinedScheme = schemePath
ColorSchemeService.applyScheme(schemePath)
}
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
schemeCard.scale = root.cardScaleHigh
}
onExited: {
schemeCard.scale = root.cardScaleLow
}
}
// Card content
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginXL * scaling
spacing: Style.marginSmall * scaling
// Scheme name
NText {
text: {
// Remove json and the full path
var chunks = schemePath.replace(".json", "").split("/")
return chunks[chunks.length - 1]
}
font.pointSize: Style.fontSizeMedium * scaling
font.weight: Style.fontWeightBold
color: getSchemeColor(modelData, "mOnSurface")
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
// Color swatches
RowLayout {
spacing: Style.marginSmall * scaling
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
// Primary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mPrimary")
}
// Secondary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mSecondary")
}
// Tertiary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
}
// Error color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mError")
}
}
}
// Selection indicator
Rectangle {
visible: Settings.data.colorSchemes.predefinedScheme === schemePath
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Style.marginSmall * scaling
width: 24 * scaling
height: 24 * scaling
radius: width * 0.5
color: Color.mPrimary
NText {
anchors.centerIn: parent
text: "✓"
font.pointSize: Style.fontSizeSmall * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
}
// Smooth animations
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationNormal
}
}
Behavior on border.width {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}
}