Compare commits

..

15 Commits

34 changed files with 933 additions and 548 deletions

View File

@@ -32,17 +32,19 @@ shell.qml Entry point: Scope { Bar {}; Notification {} }
│ └── Notification.qml Popup notification stack window
├── modules/ Feature widgets, grouped by surface
│ ├── bar/ Widgets placed in the bar (Clock, Workspaces,
│ │ BatteryIndicator, CavaVisualizer, SystemTray,
│ │ LauncherButton, PowerProfileWidget, GhostButton)
│ │ BatteryIndicator, AudioWidget, CavaVisualizer,
│ │ SystemTray, LauncherButton, PowerProfileWidget,
│ │ GhostButton)
│ └── osd/ NotificationCard.qml
├── services/ QML Singletons exposing system state
│ ├── Time.qml SystemClock → formatted time string
│ ├── Battery.qml UPower display device → percentage/icon
│ ├── Audio.qml PipeWire default sink/source → device names/icons
│ ├── PowerProfile.qml UPower power profiles → name/icon
│ ├── 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 base16 palettes (Theme.colors.base00-0F + semantic aliases), 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
@@ -64,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,5 +1,5 @@
[general]
bars = 12
bars = 14
framerate = 60
[input]

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 9C4.41421 9 4.75 9.33579 4.75 9.75V10.75C4.75 14.7541 7.99594 18 12 18C16.0041 18 19.25 14.7541 19.25 10.75V9.75C19.25 9.33579 19.5858 9 20 9C20.4142 9 20.75 9.33579 20.75 9.75V10.75C20.75 15.3298 17.2314 19.0879 12.75 19.4683V21.75C12.75 22.1642 12.4142 22.5 12 22.5C11.5858 22.5 11.25 22.1642 11.25 21.75V19.4683C6.7686 19.0879 3.25 15.3298 3.25 10.75V9.75C3.25 9.33579 3.58579 9 4 9Z" fill="#1C274C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C8.82436 2 6.25 4.57436 6.25 7.75V10.75C6.25 13.9256 8.82436 16.5 12 16.5C15.1756 16.5 17.75 13.9256 17.75 10.75V7.75C17.75 4.57436 15.1756 2 12 2ZM14 11.5C14.4142 11.5 14.75 11.1642 14.75 10.75C14.75 10.3358 14.4142 10 14 10H10C9.58579 10 9.25 10.3358 9.25 10.75C9.25 11.1642 9.58579 11.5 10 11.5H14ZM13.75 7.75C13.75 8.16421 13.4142 8.5 13 8.5H11C10.5858 8.5 10.25 8.16421 10.25 7.75C10.25 7.33579 10.5858 7 11 7H13C13.4142 7 13.75 7.33579 13.75 7.75Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

5
assets/icons/speaker.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 10.75C10.2051 10.75 8.75 12.2051 8.75 14C8.75 15.7949 10.2051 17.25 12 17.25C13.7949 17.25 15.25 15.7949 15.25 14C15.25 12.2051 13.7949 10.75 12 10.75Z" fill="#1C274C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 10C4 6.22876 4 4.34315 5.17157 3.17157C6.34315 2 8.22876 2 12 2C15.7712 2 17.6569 2 18.8284 3.17157C20 4.34315 20 6.22876 20 10V14C20 17.7712 20 19.6569 18.8284 20.8284C17.6569 22 15.7712 22 12 22C8.22876 22 6.34315 22 5.17157 20.8284C4 19.6569 4 17.7712 4 14V10ZM7.25 14C7.25 11.3766 9.37665 9.25 12 9.25C14.6234 9.25 16.75 11.3766 16.75 14C16.75 16.6234 14.6234 18.75 12 18.75C9.37665 18.75 7.25 16.6234 7.25 14ZM10 5.25C9.58579 5.25 9.25 5.58579 9.25 6C9.25 6.41421 9.58579 6.75 10 6.75H14C14.4142 6.75 14.75 6.41421 14.75 6C14.75 5.58579 14.4142 5.25 14 5.25H10Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

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"
}
}

View File

@@ -11,22 +11,9 @@ Singleton {
return `${Quickshell.shellPath("modules/bar")}/${name}.qml`;
}
property var left: [
root.widget("LauncherButton"),
root.widget("PowerProfileWidget"),
root.widget("HomeWidget"),
]
property var left: [root.widget("LauncherButton"), root.widget("PowerProfileWidget"), root.widget("HomeWidget"), root.widget("MusicWidget")]
property var center: [
root.widget("CavaVisualizer"),
root.widget("Workspaces"),
root.widget("CavaVisualizer"),
]
property var center: [root.widget("CavaVisualizer"), root.widget("Workspaces"), root.widget("CavaVisualizer"),]
property var right: [
root.widget("BatteryIndicator"),
root.widget("SystemTrayWidget"),
root.widget("ClockWidget"),
root.widget("GhostButton")
]
property var right: [root.widget("AudioWidget"), root.widget("BatteryIndicator"), root.widget("SystemTrayWidget"), root.widget("ClockWidget"), root.widget("GhostButton")]
}

150
config/Theme.qml Normal file
View File

@@ -0,0 +1,150 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property string name: "poimandres"
readonly property Base16 scheme: {
switch (root.name) {
case "poimandres":
return poimandres;
default:
return gruvbox;
}
}
component Base16: QtObject {
property color base00
property color base01
property color base02
property color base03
property color base04
property color base05
property color base06
property color base07
property color base08
property color base09
property color base0A
property color base0B
property color base0C
property color base0D
property color base0E
property color base0F
}
Base16 {
id: poimandres
base00: "#0f0f0f"
base01: "#181818"
base02: "#202020"
base03: "#292929"
base04: "#3f3f3f"
base05: "#E4F0FB"
base06: "#b6d7f4"
base07: "#ffffff"
base08: "#D0679D"
base09: "#91B4D5"
base0A: "#FFFAC2"
base0B: "#5FB3A1"
base0C: "#5DE4C7"
base0D: "#89DDFF"
base0E: "#A6ACCD"
base0F: "#FCC5E9"
}
readonly property QtObject colors: QtObject {
readonly property color base00: root.scheme.base00
readonly property color base01: root.scheme.base01
readonly property color base02: root.scheme.base02
readonly property color base03: root.scheme.base03
readonly property color base04: root.scheme.base04
readonly property color base05: root.scheme.base05
readonly property color base06: root.scheme.base06
readonly property color base07: root.scheme.base07
readonly property color base08: root.scheme.base08
readonly property color base09: root.scheme.base09
readonly property color base0A: root.scheme.base0A
readonly property color base0B: root.scheme.base0B
readonly property color base0C: root.scheme.base0C
readonly property color base0D: root.scheme.base0D
readonly property color base0E: root.scheme.base0E
readonly property color base0F: root.scheme.base0F
readonly property color accent: base0D
readonly property color foreground: base05
readonly property color background: base00
readonly property color inActive: base01
readonly property color hover: base02
readonly property color border: base03
readonly property color secondaryForeground: base04
readonly property color error: base08
readonly property color warning: base0A
readonly property color success: base0B
readonly property color info: base0C
}
property int margin: 8
property int radius: 8
property int spacing: 8
property int padding: 8
property int duration: 150
property int borderWidth: 1
property QtObject 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
}
property QtObject bar: QtObject {
property int thickness: 10
property int height: 50
}
property QtObject workspaces: QtObject {
property int height: 15
property int radius: 1000
property real activeWidthRatio: 2.3
}
property QtObject cava: QtObject {
property int width: 96
property int height: 32
property int innerMargin: 8
property int barSpacing: 3
property int barMinHeight: 2
}
property QtObject button: QtObject {
property int iconSize: 14
property int verticalPadding: 4
property int borderWidth: 2
}
property QtObject tray: QtObject {
property int iconSize: 16
}
property QtObject notification: QtObject {
property int width: 420
property int iconSize: 40
property int textSpacing: 2
property real bodyOpacity: 0.8
}
property QtObject volume: QtObject {
property int width: 300
property int height: 48
property int iconSize: 22
property int barHeight: 10
property int timeout: 1500
property real bottomMarginRatio: 0.2
}
}

View File

@@ -38,12 +38,20 @@
programs = {
nixfmt.enable = true;
qmlformat.enable = true;
};
};
in
{
rec {
formatter.${system} = treefmtEval.config.build.wrapper;
packages.${system} = {
tshell = pkgs.callPackage ./package.nix {
inherit quickshellWithModules;
};
default = packages.${system}.tshell;
};
devShells.${system}.default = pkgs.mkShell {
packages = [
quickshellWithModules

View File

@@ -0,0 +1,21 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.ui
import qs.services
RowLayout {
BarButton {
hoverHighlight: false
pointerCursor: false
label: Audio.inputName
iconSource: Audio.inputIcon
}
BarButton {
hoverHighlight: false
pointerCursor: false
label: Audio.outputName
iconSource: Audio.outputIcon
}
}

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,17 +34,11 @@ 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
}
}
}
}
}

View File

@@ -5,5 +5,6 @@ import qs.ui
BarButton {
iconOnly: true
hoverHighlight: false
iconSource: `${Quickshell.shellPath("assets")}/icons/ghost.svg`
}

View File

@@ -5,6 +5,7 @@ import qs.ui
BarButton {
iconOnly: true
hoverHighlight: false
iconSource: `${Quickshell.shellPath("assets")}/icons/nix.svg`
onClicked: Quickshell.execDetached(["vicinae", "toggle"])
}

View File

@@ -0,0 +1,17 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.ui
BarButton {
id: root
readonly property string track: Media.trackArtist !== "" ? `${Media.trackTitle} ${Media.trackArtist}` : Media.trackTitle
readonly property bool active: track !== ""
visible: active
hoverHighlight: false
pointerCursor: false
iconSource: Media.icon
label: `${Media.truncateText(root.track, 28)} ${Media.position} / ${Media.length}`
}

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
@@ -114,4 +114,3 @@ Rectangle {
}
}
}

View File

@@ -0,0 +1,42 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.config
import qs.services
import qs.ui
BarButton {
id: root
readonly property real level: Math.max(0, Math.min(Audio.muted ? 0 : Audio.volume, 1))
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/speaker.svg`
hoverHighlight: false
pointerCursor: false
iconOnly: true
iconSource: root.iconUrl
icon.width: Theme.volume.iconSize
icon.height: Theme.volume.iconSize
implicitWidth: Theme.volume.width
implicitHeight: Theme.volume.height
Rectangle {
id: fill
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
implicitWidth: parent.width * root.level
radius: Theme.radius
color: Audio.muted ? Theme.colors.error : Theme.colors.accent
opacity: 0.35
Behavior on width {
NumberAnimation {
duration: Theme.duration
}
}
}
}

68
package.nix Normal file
View File

@@ -0,0 +1,68 @@
{
lib,
stdenv,
makeWrapper,
makeFontsConf,
quickshellWithModules,
cava,
wallust,
nerd-fonts,
extraRuntimeDeps ? [ ],
}:
let
version = "0.1.0";
runtimeDeps = [
cava
wallust
]
++ extraRuntimeDeps;
fontconfig = makeFontsConf {
fontDirectories = [ nerd-fonts.fira-code ];
};
in
stdenv.mkDerivation {
inherit version;
pname = "tshell";
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./shell.qml
./assets
./config
./modules
./services
./ui
./windows
];
};
nativeBuildInputs = [ makeWrapper ];
buildInputs = [ quickshellWithModules ];
propagatedBuildInputs = runtimeDeps;
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out/bin $out/share/tshell
cp -r . $out/share/tshell
makeWrapper ${quickshellWithModules}/bin/qs $out/bin/tshell \
--prefix PATH : "${lib.makeBinPath runtimeDeps}" \
--set FONTCONFIG_FILE "${fontconfig}" \
--add-flags "-p $out/share/tshell"
runHook postInstall
'';
meta = {
description = "tux's widgets for wayland";
homepage = "https://github.com/tuxdotrs/tshell";
license = lib.licenses.gpl3Only;
mainProgram = "tshell";
platforms = lib.platforms.linux;
};
}

30
services/Audio.qml Normal file
View File

@@ -0,0 +1,30 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
Singleton {
id: root
readonly property string outputName: truncateText(root.displayName(Pipewire.defaultAudioSink), 14)
readonly property string inputName: truncateText(root.displayName(Pipewire.defaultAudioSource), 14)
readonly property url outputIcon: `${Quickshell.shellPath("assets")}/icons/speaker.svg`
readonly property url inputIcon: `${Quickshell.shellPath("assets")}/icons/microphone.svg`
readonly property real volume: Pipewire.defaultAudioSink?.audio.volume ?? 0
readonly property bool muted: Pipewire.defaultAudioSink?.audio.muted ?? false
function truncateText(text, maxLength) {
return text.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
}
function displayName(node: PwNode): string {
if (!node)
return "none";
return node.description || node.name;
}
PwObjectTracker {
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
}
}

View File

@@ -7,7 +7,7 @@ import Quickshell.Services.UPower
Singleton {
id: root
readonly property int percentage: Math.round(UPower.displayDevice.percentage * 100)
readonly property real energyRate: UPower.displayDevice.changeRate
readonly property real energyRate: UPower.displayDevice.changeRate.toFixed(1)
readonly property string iconName: root.percentage < 30 ? "battery-low.svg" : root.percentage < 70 ? "battery-medium.svg" : "battery-full.svg"
readonly property url icon: `${Quickshell.shellPath("assets")}/icons/${root.iconName}`
}

43
services/Media.qml Normal file
View File

@@ -0,0 +1,43 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
Singleton {
id: root
readonly property MprisPlayer player: {
const players = [...Mpris.players.values];
return players.find(p => p.playbackState === MprisPlaybackState.Playing) ?? players[0] ?? null;
}
readonly property bool playing: root.player?.isPlaying ?? false
readonly property string trackTitle: root.player?.trackTitle ?? ""
readonly property string trackArtist: root.player?.trackArtist ?? ""
readonly property url icon: {
const entry = root.player?.desktopEntry ?? "";
return entry !== "" ? Quickshell.iconPath(entry, false) : "";
}
readonly property string position: root.formatTime(root.player?.position ?? 0)
readonly property string length: root.formatTime(root.player?.length ?? 0)
function truncateText(text, maxLength) {
return text.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
}
function formatTime(seconds: real): string {
const total = Math.max(0, Math.floor(seconds));
const minutes = Math.floor(total / 60);
return `${minutes}:${(total % 60).toString().padStart(2, "0")}`;
}
// `position` does not update reactively on its own; emit the changed
// signal every second while playing so bindings stay in sync.
Timer {
running: root.playing && (root.player?.positionSupported ?? false)
interval: 1000
repeat: true
triggeredOnStart: true
onTriggered: root.player?.positionChanged()
}
}

View File

@@ -7,7 +7,6 @@ import Quickshell.Services.UPower as UPower
Singleton {
id: root
readonly property bool performanceAvailable: UPower.PowerProfiles.hasPerformanceProfile
readonly property string name: {
switch (UPower.PowerProfiles.profile) {
case UPower.PowerProfile.Performance:

View File

@@ -4,5 +4,5 @@ import qs.windows
Scope {
Bar {}
Notification {}
Volume {}
}

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()

52
windows/Volume.qml Normal file
View File

@@ -0,0 +1,52 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.config
import qs.services
import "../modules/osd"
PanelWindow {
id: root
property bool shown: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
visible: shown
implicitWidth: card.implicitWidth
implicitHeight: card.implicitHeight
anchors.bottom: true
margins.bottom: screen.height * Theme.volume.bottomMarginRatio
// An empty click mask prevents the window from blocking mouse events.
mask: Region {}
Connections {
target: Audio
function onVolumeChanged() {
root.shown = true;
hideTimer.restart();
}
function onMutedChanged() {
root.shown = true;
hideTimer.restart();
}
}
Timer {
id: hideTimer
interval: Theme.volume.timeout
onTriggered: root.shown = false
}
VolumeCard {
id: card
anchors.fill: parent
}
}