dynamic color scheme progress

This commit is contained in:
Zacharias-Brohn
2025-11-22 17:33:08 +01:00
parent 71e1b6aa08
commit d0db9a14d7
20 changed files with 794 additions and 35 deletions
+1
View File
@@ -3,3 +3,4 @@
.qmlls.ini
build/
compile_commands.json
testpython
+12 -2
View File
@@ -39,9 +39,13 @@ Scope {
Rectangle {
id: backgroundRect
anchors.fill: parent
color: Config.baseBgColor
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor
radius: 0
Behavior on color {
CAnim {}
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 5
@@ -92,11 +96,17 @@ Scope {
Text {
id: notificationCenterIcon
property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white"
Layout.alignment: Qt.AlignVCenter
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
font.family: "Material Symbols Rounded"
font.pixelSize: 20
color: "white"
color: iconColor
Behavior on color {
CAnim {}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
+7 -1
View File
@@ -1,6 +1,12 @@
import QtQuick
import qs.Config
import qs.Modules
Text {
text: Time.time
color: "white"
color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white"
Behavior on color {
CAnim {}
}
}
+2
View File
@@ -17,6 +17,7 @@ Singleton {
property alias colors: adapter.colors
property alias gpuType: adapter.gpuType
property alias background: adapter.background
property alias useDynamicColors: adapter.useDynamicColors
FileView {
id: root
@@ -42,6 +43,7 @@ Singleton {
property Colors colors: Colors {}
property string gpuType: ""
property BackgroundConfig background: BackgroundConfig {}
property bool useDynamicColors: false
}
}
}
+217
View File
@@ -0,0 +1,217 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import QtQuick
import ZShell
import qs.Helpers
import qs.Paths
Singleton {
id: root
property bool showPreview
property string scheme
property string flavour
readonly property bool light: showPreview ? previewLight : currentLight
property bool currentLight
property bool previewLight
readonly property M3Palette palette: showPreview ? preview : current
readonly property M3TPalette tPalette: M3TPalette {}
readonly property M3Palette current: M3Palette {}
readonly property M3Palette preview: M3Palette {}
readonly property Transparency transparency: Transparency {}
readonly property alias wallLuminance: analyser.luminance
function getLuminance(c: color): real {
if (c.r == 0 && c.g == 0 && c.b == 0)
return 0;
return Math.sqrt(0.299 * (c.r ** 2) + 0.587 * (c.g ** 2) + 0.114 * (c.b ** 2));
}
function alterColor(c: color, a: real, layer: int): color {
const luminance = getLuminance(c);
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
const b = Math.max(0, Math.min(1, c.b * scale));
return Qt.rgba(r, g, b, a);
}
function layer(c: color, layer: var): color {
if (!transparency.enabled)
return c;
return layer === 0 ? Qt.alpha(c, transparency.base) : alterColor(c, transparency.layers, layer ?? 1);
}
function on(c: color): color {
if (c.hslLightness < 0.5)
return Qt.hsla(c.hslHue, c.hslSaturation, 0.9, 1);
return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1);
}
function load(data: string, isPreview: bool): void {
const colors = isPreview ? preview : current;
const scheme = JSON.parse(data);
if (!isPreview) {
root.scheme = scheme.name;
flavour = scheme.flavour;
currentLight = scheme.mode === "light";
} else {
previewLight = scheme.mode === "light";
}
for (const [name, color] of Object.entries(scheme.colors)) {
const propName = name.startsWith("term") ? name : `m3${name}`;
if (colors.hasOwnProperty(propName))
colors[propName] = `${color}`;
}
}
FileView {
path: `${Paths.state}/scheme.json`
watchChanges: true
onFileChanged: reload()
onLoaded: root.load(text(), false)
}
ImageAnalyser {
id: analyser
source: WallpaperPath.currentWallpaperPath
}
component Transparency: QtObject {
readonly property bool enabled: true
readonly property real base: 0.85 - (root.light ? 0.1 : 0)
readonly property real layers: 0.4
}
component M3TPalette: QtObject {
readonly property color m3primary_paletteKeyColor: root.layer(root.palette.m3primary_paletteKeyColor)
readonly property color m3secondary_paletteKeyColor: root.layer(root.palette.m3secondary_paletteKeyColor)
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
readonly property color m3neutral_paletteKeyColor: root.layer(root.palette.m3neutral_paletteKeyColor)
readonly property color m3neutral_variant_paletteKeyColor: root.layer(root.palette.m3neutral_variant_paletteKeyColor)
readonly property color m3background: root.layer(root.palette.m3background, 0)
readonly property color m3onBackground: root.layer(root.palette.m3onBackground)
readonly property color m3surface: root.layer(root.palette.m3surface, 0)
readonly property color m3surfaceDim: root.layer(root.palette.m3surfaceDim, 0)
readonly property color m3surfaceBright: root.layer(root.palette.m3surfaceBright, 0)
readonly property color m3surfaceContainerLowest: root.layer(root.palette.m3surfaceContainerLowest)
readonly property color m3surfaceContainerLow: root.layer(root.palette.m3surfaceContainerLow)
readonly property color m3surfaceContainer: root.layer(root.palette.m3surfaceContainer)
readonly property color m3surfaceContainerHigh: root.layer(root.palette.m3surfaceContainerHigh)
readonly property color m3surfaceContainerHighest: root.layer(root.palette.m3surfaceContainerHighest)
readonly property color m3onSurface: root.layer(root.palette.m3onSurface)
readonly property color m3surfaceVariant: root.layer(root.palette.m3surfaceVariant, 0)
readonly property color m3onSurfaceVariant: root.layer(root.palette.m3onSurfaceVariant)
readonly property color m3inverseSurface: root.layer(root.palette.m3inverseSurface, 0)
readonly property color m3inverseOnSurface: root.layer(root.palette.m3inverseOnSurface)
readonly property color m3outline: root.layer(root.palette.m3outline)
readonly property color m3outlineVariant: root.layer(root.palette.m3outlineVariant)
readonly property color m3shadow: root.layer(root.palette.m3shadow)
readonly property color m3scrim: root.layer(root.palette.m3scrim)
readonly property color m3surfaceTint: root.layer(root.palette.m3surfaceTint)
readonly property color m3primary: root.layer(root.palette.m3primary)
readonly property color m3onPrimary: root.layer(root.palette.m3onPrimary)
readonly property color m3primaryContainer: root.layer(root.palette.m3primaryContainer)
readonly property color m3onPrimaryContainer: root.layer(root.palette.m3onPrimaryContainer)
readonly property color m3inversePrimary: root.layer(root.palette.m3inversePrimary)
readonly property color m3secondary: root.layer(root.palette.m3secondary)
readonly property color m3onSecondary: root.layer(root.palette.m3onSecondary)
readonly property color m3secondaryContainer: root.layer(root.palette.m3secondaryContainer)
readonly property color m3onSecondaryContainer: root.layer(root.palette.m3onSecondaryContainer)
readonly property color m3tertiary: root.layer(root.palette.m3tertiary)
readonly property color m3onTertiary: root.layer(root.palette.m3onTertiary)
readonly property color m3tertiaryContainer: root.layer(root.palette.m3tertiaryContainer)
readonly property color m3onTertiaryContainer: root.layer(root.palette.m3onTertiaryContainer)
readonly property color m3error: root.layer(root.palette.m3error)
readonly property color m3onError: root.layer(root.palette.m3onError)
readonly property color m3errorContainer: root.layer(root.palette.m3errorContainer)
readonly property color m3onErrorContainer: root.layer(root.palette.m3onErrorContainer)
readonly property color m3success: root.layer(root.palette.m3success)
readonly property color m3onSuccess: root.layer(root.palette.m3onSuccess)
readonly property color m3successContainer: root.layer(root.palette.m3successContainer)
readonly property color m3onSuccessContainer: root.layer(root.palette.m3onSuccessContainer)
readonly property color m3primaryFixed: root.layer(root.palette.m3primaryFixed)
readonly property color m3primaryFixedDim: root.layer(root.palette.m3primaryFixedDim)
readonly property color m3onPrimaryFixed: root.layer(root.palette.m3onPrimaryFixed)
readonly property color m3onPrimaryFixedVariant: root.layer(root.palette.m3onPrimaryFixedVariant)
readonly property color m3secondaryFixed: root.layer(root.palette.m3secondaryFixed)
readonly property color m3secondaryFixedDim: root.layer(root.palette.m3secondaryFixedDim)
readonly property color m3onSecondaryFixed: root.layer(root.palette.m3onSecondaryFixed)
readonly property color m3onSecondaryFixedVariant: root.layer(root.palette.m3onSecondaryFixedVariant)
readonly property color m3tertiaryFixed: root.layer(root.palette.m3tertiaryFixed)
readonly property color m3tertiaryFixedDim: root.layer(root.palette.m3tertiaryFixedDim)
readonly property color m3onTertiaryFixed: root.layer(root.palette.m3onTertiaryFixed)
readonly property color m3onTertiaryFixedVariant: root.layer(root.palette.m3onTertiaryFixedVariant)
}
component M3Palette: QtObject {
property color m3primary_paletteKeyColor: "#a8627b"
property color m3secondary_paletteKeyColor: "#8e6f78"
property color m3tertiary_paletteKeyColor: "#986e4c"
property color m3neutral_paletteKeyColor: "#807477"
property color m3neutral_variant_paletteKeyColor: "#837377"
property color m3background: "#191114"
property color m3onBackground: "#efdfe2"
property color m3surface: "#191114"
property color m3surfaceDim: "#191114"
property color m3surfaceBright: "#403739"
property color m3surfaceContainerLowest: "#130c0e"
property color m3surfaceContainerLow: "#22191c"
property color m3surfaceContainer: "#261d20"
property color m3surfaceContainerHigh: "#31282a"
property color m3surfaceContainerHighest: "#3c3235"
property color m3onSurface: "#efdfe2"
property color m3surfaceVariant: "#514347"
property color m3onSurfaceVariant: "#d5c2c6"
property color m3inverseSurface: "#efdfe2"
property color m3inverseOnSurface: "#372e30"
property color m3outline: "#9e8c91"
property color m3outlineVariant: "#514347"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#ffb0ca"
property color m3primary: "#ffb0ca"
property color m3onPrimary: "#541d34"
property color m3primaryContainer: "#6f334a"
property color m3onPrimaryContainer: "#ffd9e3"
property color m3inversePrimary: "#8b4a62"
property color m3secondary: "#e2bdc7"
property color m3onSecondary: "#422932"
property color m3secondaryContainer: "#5a3f48"
property color m3onSecondaryContainer: "#ffd9e3"
property color m3tertiary: "#f0bc95"
property color m3onTertiary: "#48290c"
property color m3tertiaryContainer: "#b58763"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#ffb4ab"
property color m3onError: "#690005"
property color m3errorContainer: "#93000a"
property color m3onErrorContainer: "#ffdad6"
property color m3success: "#B5CCBA"
property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E"
property color m3onSuccessContainer: "#D1E9D6"
property color m3primaryFixed: "#ffd9e3"
property color m3primaryFixedDim: "#ffb0ca"
property color m3onPrimaryFixed: "#39071f"
property color m3onPrimaryFixedVariant: "#6f334a"
property color m3secondaryFixed: "#ffd9e3"
property color m3secondaryFixedDim: "#e2bdc7"
property color m3onSecondaryFixed: "#2b151d"
property color m3onSecondaryFixedVariant: "#5a3f48"
property color m3tertiaryFixed: "#ffdcc3"
property color m3tertiaryFixedDim: "#f0bc95"
property color m3onTertiaryFixed: "#2f1500"
property color m3onTertiaryFixedVariant: "#623f21"
}
}
+24 -8
View File
@@ -5,6 +5,7 @@ import Quickshell.Io
import Quickshell.Services.Pipewire
import Quickshell.Widgets
import qs.Modules
import qs.Config
Item {
id: root
@@ -12,6 +13,7 @@ Item {
implicitHeight: 34
property bool expanded: false
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
Behavior on implicitWidth {
NumberAnimation {
@@ -34,8 +36,11 @@ Item {
anchors.right: parent.right
height: 22
radius: height / 2
color: "#40000000"
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000"
Behavior on color {
CAnim {}
}
// Background circle
Rectangle {
@@ -66,8 +71,11 @@ Item {
Layout.alignment: Qt.AlignVCenter
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\ue050" // volume_up icon
color: "#ffffff"
text: "\ue050" // volumeUp icon
color: root.textColor
Behavior on color {
CAnim {}
}
}
Rectangle {
@@ -87,7 +95,7 @@ Item {
implicitWidth: parent.width * ( Pipewire.defaultAudioSink?.audio.volume ?? 0 )
radius: parent.radius
color: "#ffffff"
color: root.textColor
}
Rectangle {
@@ -97,7 +105,7 @@ Item {
width: sinkVolumeMouseArea.pressed ? 25 : 12
height: sinkVolumeMouseArea.pressed ? 25 : 12
radius: width / 2
color: sinkVolumeMouseArea.containsMouse || sinkVolumeMouseArea.pressed ? "#ffffff" : "#aaaaaa"
color: sinkVolumeMouseArea.containsMouse || sinkVolumeMouseArea.pressed ? (Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff") : (Config.useDynamicColors ? DynamicColors.palette.m3onSurfaceVariant : "#aaaaaa")
border.color: "#40000000"
border.width: 2
anchors.verticalCenter: parent.verticalCenter
@@ -171,7 +179,11 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\ue029"
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? "#ff4444" : "#ffffff"
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3error : "#ff4444") : root.textColor
Behavior on color {
CAnim {}
}
}
Rectangle {
@@ -191,7 +203,11 @@ Item {
implicitWidth: parent.width * ( Pipewire.defaultAudioSource?.audio.volume ?? 0 )
radius: parent.radius
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? "#ff4444" : "#ffffff"
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3error : "#ff4444") : root.textColor
Behavior on color {
CAnim {}
}
}
Rectangle {
@@ -201,7 +217,7 @@ Item {
width: sourceVolumeMouseArea.pressed ? 25 : 12
height: sourceVolumeMouseArea.pressed ? 25 : 12
radius: width / 2
color: sourceVolumeMouseArea.containsMouse || sourceVolumeMouseArea.pressed ? "#ffffff" : "#aaaaaa"
color: sourceVolumeMouseArea.containsMouse || sourceVolumeMouseArea.pressed ? (Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff") : (Config.useDynamicColors ? DynamicColors.palette.m3onSurfaceVariant : "#aaaaaa")
border.color: "#40000000"
border.width: 2
anchors.verticalCenter: parent.verticalCenter
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import qs.Modules
ColorAnimation {
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: MaterialEasing.standard
}
+3
View File
@@ -3,6 +3,7 @@ import QtQuick
import QtQuick.Controls
import qs.Helpers
import qs.Config
import qs.Paths
TextField {
id: root
@@ -84,6 +85,8 @@ TextField {
launcherWindow.visible = false;
} else if ( wallpaperPickerLoader.active ) {
SearchWallpapers.setWallpaper(wallpaperPickerLoader.item.currentItem.modelData.path)
if ( Config.useDynamicColors )
Quickshell.execDetached(["python3", Quickshell.shellPath("scripts/SchemeColorGen.py"), `--path=${wallpaperPickerLoader.item.currentItem.modelData.path}`, `--thumbnail=${Paths.cache}/imagecache/thumbnail.jpg`, `--output=${Paths.state}/scheme.json`]);
if ( Config.wallust ) {
Wallust.generateColors(WallpaperPath.currentWallpaperPath);
}
+13 -2
View File
@@ -15,6 +15,8 @@ Item {
implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth
implicitHeight: 22
property bool warning: percentage * 100 >= warningThreshold
property color usageColor: Config.useDynamicColors ? ( warning ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary ) : ( warning ? Config.accentColor.accents.warning : Config.accentColor.accents.primary )
property color borderColor: Config.useDynamicColors ? ( warning ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary ) : ( warning ? Config.accentColor.accents.warningAlt : Config.accentColor.accents.primaryAlt )
Behavior on percentage {
NumberAnimation {
@@ -55,9 +57,14 @@ Item {
ShapePath {
strokeWidth: 0
fillColor: root.warning ? Config.accentColor.accents.warning : Config.accentColor.accents.primary
fillColor: root.usageColor
startX: backgroundCircle.width / 2
startY: backgroundCircle.height / 2
Behavior on fillColor {
CAnim {}
}
PathLine {
x: backgroundCircle.width / 2
y: 0 + ( 1 / 2 )
@@ -80,10 +87,14 @@ Item {
ShapePath {
strokeWidth: 1
strokeColor: root.warning ? Config.accentColor.accents.warningAlt : Config.accentColor.accents.primaryAlt
strokeColor: root.borderColor
fillColor: "transparent"
capStyle: ShapePath.FlatCap
Behavior on strokeColor {
CAnim {}
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
+18 -5
View File
@@ -10,6 +10,7 @@ Item {
id: root
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 34
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
Rectangle {
anchors {
@@ -18,7 +19,7 @@ Item {
verticalCenter: parent.verticalCenter
}
implicitHeight: 22
color: "#40000000"
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000"
radius: height / 2
RowLayout {
id: rowLayout
@@ -33,7 +34,10 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\uf7a3"
color: "#ffffff"
color: root.textColor
Behavior on color {
CAnim {}
}
}
Resource {
@@ -46,7 +50,10 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\ue322"
color: "#ffffff"
color: root.textColor
Behavior on color {
CAnim {}
}
}
Resource {
@@ -59,7 +66,10 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 16
text: "\ue30f"
color: "#ffffff"
color: root.textColor
Behavior on color {
CAnim {}
}
}
Resource {
@@ -71,7 +81,10 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\ue30d"
color: "#ffffff"
color: root.textColor
Behavior on color {
CAnim {}
}
}
Resource {
+3
View File
@@ -2,8 +2,11 @@ import QtQuick
import Quickshell
import Quickshell.Services.SystemTray
import Quickshell.Io
import Quickshell.Widgets
import qs.Modules
import qs.Config
import Caelestia
import QtQuick.Effects
MouseArea {
id: root
+14 -3
View File
@@ -1,17 +1,22 @@
import QtQuick
import QtQuick.Layouts
import qs.Modules
import qs.Config
Item {
id: root
required property int countUpdates
implicitWidth: contentRow.childrenRect.width + 10
implicitHeight: 22
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
Rectangle {
anchors.fill: parent
radius: height / 2
color: "#40000000"
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000"
Behavior on color {
CAnim {}
}
}
RowLayout {
@@ -28,7 +33,10 @@ Item {
font.family: "Material Symbols Rounded"
font.pixelSize: 18
text: "\uf569"
color: "#ffffff"
color: root.textColor
Behavior on color {
CAnim {}
}
}
TextMetrics {
@@ -40,7 +48,10 @@ Item {
Text {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
text: textMetrics.text
color: "white"
color: root.textColor
Behavior on color {
CAnim {}
}
}
}
}
+12 -2
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Helpers
import qs.Config
Item {
id: root
@@ -11,6 +12,7 @@ Item {
clip: true
property bool showFirst: true
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white"
Component.onCompleted: {
Hyprland.rawEvent.connect(( event ) => {
@@ -37,7 +39,7 @@ Item {
anchors.fill: parent
anchors.margins: 5
text: root.currentTitle
color: "white"
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
@@ -46,13 +48,17 @@ Item {
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on color {
CAnim {}
}
}
Text {
id: titleText2
anchors.fill: parent
anchors.margins: 5
color: "white"
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
@@ -61,5 +67,9 @@ Item {
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on color {
CAnim {}
}
}
}
+16 -10
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import qs.Config
@@ -26,7 +27,7 @@ Rectangle {
}
}
color: "#40000000"
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000"
radius: height / 2
Behavior on implicitWidth {
@@ -36,6 +37,10 @@ Rectangle {
}
}
Behavior on color {
CAnim {}
}
RowLayout {
id: workspacesRow
anchors.left: parent.left
@@ -47,15 +52,16 @@ Rectangle {
model: Hyprland.workspaces
Rectangle {
id: workspaceIndicator
required property var modelData
width: 16
height: 16
radius: height / 2
color: modelData.id === Hyprland.focusedWorkspace.id ? Config.accentColor.accents.primary : "#606060"
color: modelData.id === Hyprland.focusedWorkspace.id ? ( Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary ) : ( Config.useDynamicColors ? DynamicColors.palette.m3inverseOnSurface : "#606060" )
border.color: modelData.id === Hyprland.focusedWorkspace.id ? Config.accentColor.accents.primaryAlt : "#808080"
border.color: modelData.id === Hyprland.focusedWorkspace.id ? ( Config.useDynamicColors ? DynamicColors.palette.m3onPrimary : Config.accentColor.accents.primaryAlt ) : ( Config.useDynamicColors ? DynamicColors.palette.m3inverseOnSurface : "#808080" )
border.width: 1
visible: root.shouldShow( modelData.monitor )
@@ -90,13 +96,13 @@ Rectangle {
duration: 200
}
Text {
anchors.centerIn: parent
text: modelData.id
font.pixelSize: 10
font.family: "Rubik"
color: modelData.id === Hyprland.focusedWorkspace.id ? Config.workspaceWidget.textColor : Config.workspaceWidget.inactiveTextColor
}
// Text {
// anchors.centerIn: parent
// text: modelData.id
// font.pixelSize: 10
// font.family: "Rubik"
// color: modelData.id === Hyprland.focusedWorkspace.id ? Config.workspaceWidget.textColor : Config.workspaceWidget.inactiveTextColor
// }
MouseArea {
anchors.fill: parent
+1
View File
@@ -33,6 +33,7 @@ qml_module(ZShell
SOURCES
writefile.hpp writefile.cpp
appdb.hpp appdb.cpp
imageanalyser.hpp imageanalyser.cpp
LIBRARIES
Qt::Gui
Qt::Quick
+223
View File
@@ -0,0 +1,223 @@
#include "imageanalyser.hpp"
#include <QtConcurrent/qtconcurrentrun.h>
#include <QtQuick/qquickitemgrabresult.h>
#include <qfuturewatcher.h>
#include <qimage.h>
#include <qquickwindow.h>
namespace ZShell {
ImageAnalyser::ImageAnalyser(QObject* parent)
: QObject(parent)
, m_futureWatcher(new QFutureWatcher<AnalyseResult>(this))
, m_source("")
, m_sourceItem(nullptr)
, m_rescaleSize(128)
, m_dominantColour(0, 0, 0)
, m_luminance(0) {
QObject::connect(m_futureWatcher, &QFutureWatcher<AnalyseResult>::finished, this, [this]() {
if (!m_futureWatcher->future().isResultReadyAt(0)) {
return;
}
const auto result = m_futureWatcher->result();
if (m_dominantColour != result.first) {
m_dominantColour = result.first;
emit dominantColourChanged();
}
if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) {
m_luminance = result.second;
emit luminanceChanged();
}
});
}
QString ImageAnalyser::source() const {
return m_source;
}
void ImageAnalyser::setSource(const QString& source) {
if (m_source == source) {
return;
}
m_source = source;
emit sourceChanged();
if (m_sourceItem) {
m_sourceItem = nullptr;
emit sourceItemChanged();
}
requestUpdate();
}
QQuickItem* ImageAnalyser::sourceItem() const {
return m_sourceItem;
}
void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) {
if (m_sourceItem == sourceItem) {
return;
}
m_sourceItem = sourceItem;
emit sourceItemChanged();
if (!m_source.isEmpty()) {
m_source = "";
emit sourceChanged();
}
requestUpdate();
}
int ImageAnalyser::rescaleSize() const {
return m_rescaleSize;
}
void ImageAnalyser::setRescaleSize(int rescaleSize) {
if (m_rescaleSize == rescaleSize) {
return;
}
m_rescaleSize = rescaleSize;
emit rescaleSizeChanged();
requestUpdate();
}
QColor ImageAnalyser::dominantColour() const {
return m_dominantColour;
}
qreal ImageAnalyser::luminance() const {
return m_luminance;
}
void ImageAnalyser::requestUpdate() {
if (m_source.isEmpty() && !m_sourceItem) {
return;
}
if (!m_sourceItem || (m_sourceItem->window() && m_sourceItem->window()->isVisible() && m_sourceItem->width() > 0 &&
m_sourceItem->height() > 0)) {
update();
} else if (m_sourceItem) {
if (!m_sourceItem->window()) {
QObject::connect(m_sourceItem, &QQuickItem::windowChanged, this, &ImageAnalyser::requestUpdate,
Qt::SingleShotConnection);
} else if (!m_sourceItem->window()->isVisible()) {
QObject::connect(m_sourceItem->window(), &QQuickWindow::visibleChanged, this, &ImageAnalyser::requestUpdate,
Qt::SingleShotConnection);
}
if (m_sourceItem->width() <= 0) {
QObject::connect(
m_sourceItem, &QQuickItem::widthChanged, this, &ImageAnalyser::requestUpdate, Qt::SingleShotConnection);
}
if (m_sourceItem->height() <= 0) {
QObject::connect(m_sourceItem, &QQuickItem::heightChanged, this, &ImageAnalyser::requestUpdate,
Qt::SingleShotConnection);
}
}
}
void ImageAnalyser::update() {
if (m_source.isEmpty() && !m_sourceItem) {
return;
}
if (m_futureWatcher->isRunning()) {
m_futureWatcher->cancel();
}
if (m_sourceItem) {
const QSharedPointer<const QQuickItemGrabResult> grabResult = m_sourceItem->grabToImage();
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, this]() {
m_futureWatcher->setFuture(QtConcurrent::run(&ImageAnalyser::analyse, grabResult->image(), m_rescaleSize));
});
} else {
m_futureWatcher->setFuture(QtConcurrent::run([=, this](QPromise<AnalyseResult>& promise) {
const QImage image(m_source);
analyse(promise, image, m_rescaleSize);
}));
}
}
void ImageAnalyser::analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize) {
if (image.isNull()) {
qWarning() << "ImageAnalyser::analyse: image is null";
return;
}
QImage img = image;
if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) {
img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation);
}
if (promise.isCanceled()) {
return;
}
if (img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32);
}
if (promise.isCanceled()) {
return;
}
const uchar* data = img.bits();
const int width = img.width();
const int height = img.height();
const qsizetype bytesPerLine = img.bytesPerLine();
std::unordered_map<quint32, int> colours;
qreal totalLuminance = 0.0;
int count = 0;
for (int y = 0; y < height; ++y) {
const uchar* line = data + y * bytesPerLine;
for (int x = 0; x < width; ++x) {
if (promise.isCanceled()) {
return;
}
const uchar* pixel = line + x * 4;
if (pixel[3] == 0) {
continue;
}
const quint32 mr = static_cast<quint32>(pixel[0] & 0xF8);
const quint32 mg = static_cast<quint32>(pixel[1] & 0xF8);
const quint32 mb = static_cast<quint32>(pixel[2] & 0xF8);
++colours[(mr << 16) | (mg << 8) | mb];
const qreal r = pixel[0] / 255.0;
const qreal g = pixel[1] / 255.0;
const qreal b = pixel[2] / 255.0;
totalLuminance += std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
++count;
}
}
quint32 dominantColour = 0;
int maxCount = 0;
for (const auto& [colour, colourCount] : colours) {
if (promise.isCanceled()) {
return;
}
if (colourCount > maxCount) {
dominantColour = colour;
maxCount = colourCount;
}
}
promise.addResult(qMakePair(QColor((0xFFu << 24) | dominantColour), count == 0 ? 0.0 : totalLuminance / count));
}
} // namespace ZShell
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <QtQuick/qquickitem.h>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qobject.h>
#include <qqmlintegration.h>
namespace ZShell {
class ImageAnalyser : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged)
Q_PROPERTY(int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged)
Q_PROPERTY(QColor dominantColour READ dominantColour NOTIFY dominantColourChanged)
Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged)
public:
explicit ImageAnalyser(QObject* parent = nullptr);
[[nodiscard]] QString source() const;
void setSource(const QString& source);
[[nodiscard]] QQuickItem* sourceItem() const;
void setSourceItem(QQuickItem* sourceItem);
[[nodiscard]] int rescaleSize() const;
void setRescaleSize(int rescaleSize);
[[nodiscard]] QColor dominantColour() const;
[[nodiscard]] qreal luminance() const;
Q_INVOKABLE void requestUpdate();
signals:
void sourceChanged();
void sourceItemChanged();
void rescaleSizeChanged();
void dominantColourChanged();
void luminanceChanged();
private:
using AnalyseResult = QPair<QColor, qreal>;
QFutureWatcher<AnalyseResult>* const m_futureWatcher;
QString m_source;
QQuickItem* m_sourceItem;
int m_rescaleSize;
QColor m_dominantColour;
qreal m_luminance;
void update();
static void analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize);
};
} // namespace ZShell
-1
View File
@@ -1,4 +1,3 @@
// Time.qml
pragma Singleton
import Quickshell
+56
View File
@@ -0,0 +1,56 @@
{
"primary_paletteKeyColor": "#A56A24",
"secondary_paletteKeyColor": "#587C8E",
"tertiary_paletteKeyColor": "#3A8188",
"neutral_paletteKeyColor": "#6F7979",
"neutral_variant_paletteKeyColor": "#697A7B",
"background": "#0C1515",
"onBackground": "#DAE4E4",
"surface": "#0C1515",
"surfaceDim": "#0C1515",
"surfaceBright": "#313B3B",
"surfaceContainerLowest": "#071010",
"surfaceContainerLow": "#141D1E",
"surfaceContainer": "#182122",
"surfaceContainerHigh": "#222C2C",
"surfaceContainerHighest": "#2D3637",
"onSurface": "#DAE4E4",
"surfaceVariant": "#394A4A",
"onSurfaceVariant": "#B8CACA",
"inverseSurface": "#DAE4E4",
"inverseOnSurface": "#293232",
"outline": "#829494",
"outlineVariant": "#394A4A",
"shadow": "#000000",
"scrim": "#000000",
"surfaceTint": "#FFB86E",
"primary": "#FFB86E",
"onPrimary": "#492900",
"primaryContainer": "#693C00",
"onPrimaryContainer": "#FFDCBD",
"inversePrimary": "#88520A",
"secondary": "#A6CCE0",
"onSecondary": "#0A3545",
"secondaryContainer": "#284E5E",
"onSecondaryContainer": "#C2E8FD",
"tertiary": "#8CD2D9",
"onTertiary": "#00363B",
"tertiaryContainer": "#569BA2",
"onTertiaryContainer": "#000000",
"error": "#FFB4AB",
"onError": "#690005",
"errorContainer": "#93000A",
"onErrorContainer": "#FFDAD6",
"primaryFixed": "#FFDCBD",
"primaryFixedDim": "#FFB86E",
"onPrimaryFixed": "#2C1600",
"onPrimaryFixedVariant": "#693C00",
"secondaryFixed": "#C2E8FD",
"secondaryFixedDim": "#A6CCE0",
"onSecondaryFixed": "#001F2A",
"onSecondaryFixedVariant": "#264B5C",
"tertiaryFixed": "#A8EEF6",
"tertiaryFixedDim": "#8CD2D9",
"onTertiaryFixed": "#002022",
"onTertiaryFixedVariant": "#004F55"
}
+102
View File
@@ -0,0 +1,102 @@
import json
import argparse
from pathlib import Path
from PIL import Image
from materialyoucolor.quantize import QuantizeCelebi
from materialyoucolor.score.score import Score
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
from materialyoucolor.hct.hct import Hct
def generate_thumbnail(image_path, thumbnail_path, size=(128, 128)):
thumbnail_file = Path(thumbnail_path)
image = Image.open(image_path)
image = image.convert("RGB")
image.thumbnail(size, Image.NEAREST)
thumbnail_file.parent.mkdir(parents=True, exist_ok=True)
image.save(thumbnail_path, "JPEG")
def generate_color_scheme(thumbnail_path, output_path):
image = Image.open(thumbnail_path)
pixel_len = image.width * image.height
image_data = image.getdata()
quality = 1
pixel_array = [image_data[_] for _ in range(0, pixel_len, quality)]
result = QuantizeCelebi(pixel_array, 128)
score = Score.score(result)[0]
scheme = SchemeTonalSpot(
Hct.from_int(score),
True,
0.0
)
color_dict = {}
for color in vars(MaterialDynamicColors).keys():
color_name = getattr(MaterialDynamicColors, color)
if hasattr(color_name, "get_hct"):
color_int = color_name.get_hct(scheme).to_int()
color_dict[color] = int_to_hex(color_int)
output_dict = {
"name": "dynamic",
"flavour": "default",
"mode": "dark",
"variant": "tonalspot",
"colors": color_dict
}
output_file = Path(output_path)
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, "w") as f:
json.dump(output_dict, f, indent=4)
def int_to_hex(argb_int):
return "#{:06X}".format(argb_int & 0xFFFFFF)
def main():
parser = argparse.ArgumentParser(
description="Generate color scheme from wallpaper image"
)
parser.add_argument(
"--path",
required=True,
help="Path to the wallpaper image"
)
parser.add_argument(
"--output",
required=True,
help="Path to save the color scheme JSON file"
)
parser.add_argument(
"--thumbnail",
required=True,
help="Path to save the thumbnail image"
)
args = parser.parse_args()
try:
generate_thumbnail(args.path, str(args.thumbnail))
generate_color_scheme(str(args.thumbnail), args.output)
except Exception as e:
print(f"Error: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())