326 lines
11 KiB
QML
326 lines
11 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 (supports dark/light variants)
|
|
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]) {
|
|
var entry = schemeColorsCache[schemeName]
|
|
var variant = entry
|
|
if (entry.dark || entry.light) {
|
|
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark)
|
|
}
|
|
if (variant && variant[colorKey]) return variant[colorKey]
|
|
}
|
|
|
|
// Return a default color if not cached yet
|
|
return "#000000"
|
|
}
|
|
|
|
// Cache for scheme JSON (can be flat or {dark, light})
|
|
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 value = jsonData || {}
|
|
var newCache = schemeColorsCache
|
|
newCache[schemeName] = value
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
|
NToggle {
|
|
label: "Dark Mode"
|
|
description: Settings.data.colorSchemes.useWallpaperColors
|
|
? "Generate dark theme colors when using Matugen. Disable for light theme."
|
|
: "If the selected predefined scheme has light/dark variants, this chooses which one."
|
|
checked: Settings.data.colorSchemes.darkMode
|
|
enabled: true
|
|
onToggled: checked => {
|
|
Settings.data.colorSchemes.darkMode = checked
|
|
if (Settings.data.colorSchemes.useWallpaperColors) {
|
|
ColorSchemeService.changedWallpaper()
|
|
} else if (Settings.data.colorSchemes.predefinedScheme) {
|
|
// Re-apply current scheme to pick the right variant
|
|
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
|
// Force refresh of previews
|
|
var tmp = schemeColorsCache; schemeColorsCache = {}; schemeColorsCache = tmp
|
|
}
|
|
}
|
|
}
|
|
|
|
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. You can toggle between light and dark themes when using Matugen."
|
|
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
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|