refactor(theme): centralize theme

This commit is contained in:
tux
2026-08-24 16:42:02 +05:30
parent 5d84d68e26
commit e1906e5528
11 changed files with 125 additions and 104 deletions

View File

@@ -44,7 +44,7 @@ shell.qml Entry point: Scope { Bar {}; Notification {} }
│ ├── Cava.qml Spawns `cava`, parses stdout into `values[]`
│ └── Notifications.qml NotificationServer: popups, DND flag, history
├── config/ QML Singletons for configuration/theming
│ ├── Appearance.qml colors, font, margin/radius/spacing/padding/duration
│ ├── Theme.qml colors, font, margin/radius/spacing/padding/duration, per-widget sizes
│ └── BarLayout.qml Which widgets go in left/center/right bar sections
├── ui/ Shared reusable primitives
│ ├── BarButton.qml Themed button used by bar widgets
@@ -66,6 +66,6 @@ shell.qml Entry point: Scope { Bar {}; Notification {} }
- Start every QML file with `pragma ComponentBehavior: Bound` where bindings reference outer ids.
- Indentation is inconsistent across the repo (2-space in newer UI/window files, 4-space in some services); match the surrounding file when editing.
- Prefer `readonly property` for derived values; keep widgets presentational and pull all theme values from `Appearance.*` rather than hardcoding colors/sizes/fonts.
- Prefer `readonly property` for derived values; keep widgets presentational and pull all theme values from `Theme.*` rather than hardcoding colors/sizes/fonts.
- Access asset paths via `Quickshell.shellPath(...)` (see `Battery.qml`, `NotificationCard.qml`) so paths work regardless of install location.
- Commit messages follow Conventional Commits style (`feat(notifications): ...`, `refactor(bar): ...`).

View File

@@ -1,35 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property QtObject colors
property QtObject font
property int margin
property int radius
property int spacing
property int padding
property int duration
margin: 8
radius: 8
spacing: 8
padding: 8
duration: 150
font: QtObject {
property string family: "FiraCode Nerd Font Mono SemBd"
property int pointSize: 9
}
colors: QtObject {
property color accent: "#6791c9"
property color foreground: "#ffffff"
property color background: "#101213"
property color inActive: "#1b1d1e"
}
}

60
config/Theme.qml Normal file
View File

@@ -0,0 +1,60 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property int margin: 8
property int radius: 8
property int spacing: 8
property int padding: 8
property int duration: 150
font: QtObject {
property string family: "FiraCode Nerd Font Mono SemBd"
property int pointSize: 9
readonly property int body: pointSize - 1
readonly property int caption: pointSize - 2
}
colors: QtObject {
property color accent: "#6791c9"
property color foreground: "#ffffff"
property color background: "#101213"
property color inActive: "#1b1d1e"
}
bar: QtObject {
property int thickness: 10
property int height: 50
}
workspaces: QtObject {
property int height: 15
property int radius: 1000
property real activeWidthRatio: 2.3
}
cava: QtObject {
property int width: 96
property int height: 32
property int innerMargin: 8
property int barSpacing: 3
property int barMinHeight: 2
}
tray: QtObject {
property int iconSize: 16
}
notification: QtObject {
property int width: 420
property int iconSize: 40
property int borderWidth: 1
property int textSpacing: 2
property real bodyOpacity: 0.8
}
}

View File

@@ -11,18 +11,18 @@ Rectangle {
property real maxValue: 100
readonly property bool shouldVisualize: barCount > 0 && Cava.values.some(v => v >= 0.001)
implicitWidth: 96
implicitHeight: 32
radius: Appearance.radius
color: Appearance.colors.inActive
implicitWidth: Theme.cava.width
implicitHeight: Theme.cava.height
radius: Theme.radius
color: Theme.colors.inActive
visible: shouldVisualize
Row {
id: barRow
anchors.fill: parent
anchors.margins: 8
spacing: 3
anchors.margins: Theme.cava.innerMargin
spacing: Theme.cava.barSpacing
Repeater {
model: root.barCount
@@ -34,15 +34,15 @@ Rectangle {
readonly property real value: Cava.values[index] ?? 0
width: (barRow.width - barRow.spacing * (root.barCount - 1)) / root.barCount
height: Math.max(2, (bar.value / root.maxValue) * barRow.height)
height: Math.max(Theme.cava.barMinHeight, (bar.value / root.maxValue) * barRow.height)
anchors.bottom: parent.bottom
radius: bar.width / 2
color: Appearance.colors.accent
color: Theme.colors.accent
antialiasing: true
Behavior on height {
NumberAnimation {
duration: Appearance.duration
duration: Theme.duration
}
}
}

View File

@@ -8,14 +8,12 @@ import qs.ui
BarButton {
id: root
property int iconSize: 16
hoverHighlight: false
pointerCursor: false
visible: SystemTray.items.values.length > 0
contentItem: Row {
spacing: Appearance.spacing
spacing: Theme.spacing
Repeater {
model: SystemTray.items.values
@@ -24,8 +22,8 @@ BarButton {
required property var modelData
source: modelData.icon
width: root.iconSize
height: root.iconSize
width: Theme.tray.iconSize
height: Theme.tray.iconSize
}
}
}

View File

@@ -9,7 +9,7 @@ Row {
property int count: 7
spacing: Appearance.spacing
spacing: Theme.spacing
Repeater {
model: root.count
@@ -21,15 +21,15 @@ Row {
property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1)
property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1)
radius: 1000
implicitHeight: 15
implicitWidth: pill.isActive ? pill.implicitHeight * 2.3 : pill.implicitHeight
radius: Theme.workspaces.radius
implicitHeight: Theme.workspaces.height
implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight
color: {
if (handler.hovered)
return Appearance.colors.accent;
return Theme.colors.accent;
if (pill.isActive || pill.ws)
return Appearance.colors.accent;
return Appearance.colors.inActive;
return Theme.colors.accent;
return Theme.colors.inActive;
}
MouseArea {
@@ -45,13 +45,13 @@ Row {
Behavior on color {
ColorAnimation {
duration: Appearance.duration
duration: Theme.duration
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: Appearance.duration
duration: Theme.duration
easing.type: Easing.OutQuad
}
}

View File

@@ -26,12 +26,12 @@ Rectangle {
readonly property url themedIconUrl: iconName !== "" ? Quickshell.iconPath(iconName, false) : ""
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/notification.svg`
implicitWidth: 360
implicitHeight: content.implicitHeight + Appearance.padding * 2
radius: Appearance.radius
color: Appearance.colors.background
border.width: critical ? 1 : 0
border.color: Appearance.colors.accent
implicitWidth: Theme.notification.width
implicitHeight: content.implicitHeight + Theme.padding * 2
radius: Theme.radius
color: Theme.colors.background
border.width: critical ? Theme.notification.borderWidth : 0
border.color: Theme.colors.accent
Timer {
id: countdown
@@ -52,12 +52,12 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Appearance.padding
spacing: Appearance.spacing
anchors.margins: Theme.padding
spacing: Theme.spacing
Item {
width: 40
height: 40
width: Theme.notification.iconSize
height: Theme.notification.iconSize
Layout.alignment: Qt.AlignTop
Image {
@@ -77,23 +77,23 @@ Rectangle {
}
ColumnLayout {
spacing: 2
spacing: Theme.notification.textSpacing
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Text {
text: root.notif?.appName ?? ""
color: Appearance.colors.accent
font.family: Appearance.font.family
font.pointSize: Appearance.font.pointSize - 2
color: Theme.colors.accent
font.family: Theme.font.family
font.pointSize: Theme.font.caption
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: root.notif?.summary ?? ""
color: Appearance.colors.foreground
font.family: Appearance.font.family
font.pointSize: Appearance.font.pointSize
color: Theme.colors.foreground
font.family: Theme.font.family
font.pointSize: Theme.font.pointSize
font.weight: Font.DemiBold
elide: Text.ElideRight
Layout.fillWidth: true
@@ -101,10 +101,10 @@ Rectangle {
Text {
text: root.notif?.body ?? ""
visible: text !== ""
color: Appearance.colors.foreground
opacity: 0.8
font.family: Appearance.font.family
font.pointSize: Appearance.font.pointSize - 1
color: Theme.colors.foreground
opacity: Theme.notification.bodyOpacity
font.family: Theme.font.family
font.pointSize: Theme.font.body
wrapMode: Text.Wrap
maximumLineCount: 3
elide: Text.ElideRight

View File

@@ -13,14 +13,14 @@ Button {
property bool hoverHighlight: true
property bool pointerCursor: true
padding: Appearance.padding
padding: Theme.padding
display: iconOnly ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
text: label
font.family: Appearance.font.family
font.pointSize: Appearance.font.pointSize
palette.buttonText: Appearance.colors.foreground
icon.color: Appearance.colors.foreground
font.family: Theme.font.family
font.pointSize: Theme.font.pointSize
palette.buttonText: Theme.colors.foreground
icon.color: Theme.colors.foreground
icon.source: iconSource
HoverHandler {
@@ -30,16 +30,16 @@ Button {
background: Rectangle {
anchors.fill: parent
radius: Appearance.radius
radius: Theme.radius
color: {
if (!root.hoverHighlight)
return Appearance.colors.inActive;
return root.hovered ? Appearance.colors.inActive : Appearance.colors.background;
return Theme.colors.inActive;
return root.hovered ? Theme.colors.inActive : Theme.colors.background;
}
Behavior on color {
ColorAnimation {
duration: Appearance.duration
duration: Theme.duration
}
}
}

View File

@@ -9,7 +9,7 @@ WrapperItem {
id: root
property int corner
property real radius: Appearance.radius
property real radius: Theme.radius
property color color
Component.onCompleted: {

View File

@@ -10,7 +10,7 @@ import qs.ui
PanelWindow {
id: window
property color barColor: Appearance.colors.background
property color barColor: Theme.colors.background
color: "transparent"
exclusionMode: ExclusionMode.Ignore
@@ -41,7 +41,7 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter
x: section.alignment === Qt.AlignLeft ? 0 : section.alignment === Qt.AlignRight ? parent.width - width : (parent.width - width) / 2
spacing: Appearance.spacing
spacing: Theme.spacing
Repeater {
model: section.widgets
@@ -84,7 +84,7 @@ PanelWindow {
Rectangle {
id: leftBar
implicitWidth: 10
implicitWidth: Theme.bar.thickness
implicitHeight: QsWindow.window?.height ?? 0
color: window.barColor
anchors.left: parent.left
@@ -92,7 +92,7 @@ PanelWindow {
Rectangle {
id: rightBar
implicitWidth: 10
implicitWidth: Theme.bar.thickness
implicitHeight: QsWindow.window?.height ?? 0
color: window.barColor
anchors.right: parent.right
@@ -101,7 +101,7 @@ PanelWindow {
id: bottomBar
implicitWidth: QsWindow.window?.width ?? 0
implicitHeight: 10
implicitHeight: Theme.bar.thickness
color: window.barColor
anchors.bottom: parent.bottom
}
@@ -109,7 +109,7 @@ PanelWindow {
id: topBar
implicitWidth: QsWindow.window?.width ?? 0
implicitHeight: 50
implicitHeight: Theme.bar.height
color: window.barColor
anchors.top: parent.top
@@ -117,7 +117,7 @@ PanelWindow {
id: flexLayout
anchors.fill: parent
anchors.margins: Appearance.margin
anchors.margins: Theme.margin
wrap: FlexboxLayout.Wrap
direction: FlexboxLayout.Row

View File

@@ -10,12 +10,10 @@ import "../modules/osd"
PanelWindow {
id: root
readonly property int cardWidth: 420
color: "transparent"
exclusionMode: ExclusionMode.Ignore
visible: Notifications.popupCount > 0
implicitWidth: root.cardWidth
implicitWidth: Theme.notification.width
implicitHeight: stack.implicitHeight
anchors {
@@ -23,8 +21,8 @@ PanelWindow {
right: true
}
margins {
top: 50 + Appearance.margin
right: 10 + Appearance.margin
top: Theme.bar.height + Theme.margin
right: Theme.bar.thickness + Theme.margin
}
mask: Region {
@@ -37,7 +35,7 @@ PanelWindow {
anchors.top: parent.top
anchors.right: parent.right
anchors.left: parent.left
spacing: Appearance.spacing
spacing: Theme.spacing
Repeater {
model: [...Notifications.popups.values].reverse()