Inital commit checkpoint
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.qmlls.ini
|
||||||
10
GlobalStates.qml
Normal file
10
GlobalStates.qml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property bool mediaControlsOpen: false
|
||||||
|
property bool osdVolumeOpen: false
|
||||||
|
}
|
||||||
80
bar/Bar.qml
Normal file
80
bar/Bar.qml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens;
|
||||||
|
PanelWindow {
|
||||||
|
required property var modelData
|
||||||
|
screen: modelData
|
||||||
|
WlrLayershell.layer: WlrLayer.Bottom
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: barBackground
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0
|
||||||
|
}
|
||||||
|
color: Config.options.bar.showBackground ? Appearance.colors.colLayer1 : "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: 45
|
||||||
|
|
||||||
|
RowLayout { // Left Section
|
||||||
|
id: leftSection
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: 5
|
||||||
|
}
|
||||||
|
spacing: Config.options?.bar.borderless ? 4 : 8
|
||||||
|
Workspaces {}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout { // Middle section
|
||||||
|
id: middleSection
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Config.options?.bar.borderless ? 4 : 8
|
||||||
|
|
||||||
|
Media {
|
||||||
|
visible: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout { // Right Section
|
||||||
|
id: rightSection
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 5
|
||||||
|
}
|
||||||
|
spacing: Config.options?.bar.borderless ? 4 : 8
|
||||||
|
SysTray {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
NotificationIcon {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
ClockWidget {
|
||||||
|
id: clock
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
bar/BarGroup.qml
Normal file
9
bar/BarGroup.qml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import qs.modules.common
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property real padding: 5
|
||||||
|
implicitHeight: Appearance.sizes.baseBarHeight
|
||||||
|
}
|
||||||
8
bar/ClockWidget.qml
Normal file
8
bar/ClockWidget.qml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: Time.time
|
||||||
|
}
|
||||||
81
bar/Media.qml
Normal file
81
bar/Media.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
|
||||||
|
import qs
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property bool borderless: Config.options.bar.borderless
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
|
||||||
|
implicitHeight: Appearance.sizes.barHeight
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: MprisController.hasPlayers && MprisController.activePlayer().isPlaying && MprisController.activePlayer().lengthSupported
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
onTriggered: MprisController.activePlayer().positionChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton
|
||||||
|
onPressed: (event) => {
|
||||||
|
if (event.button === Qt.MiddleButton) {
|
||||||
|
MprisController.activePlayer().togglePlaying();
|
||||||
|
} else if (event.button === Qt.BackButton) {
|
||||||
|
MprisController.shiftPlayer(-1);
|
||||||
|
} else if (event.button === Qt.ForwardButton) {
|
||||||
|
MprisController.shiftPlayer(1);
|
||||||
|
} else if (event.button === Qt.LeftButton) {
|
||||||
|
GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: MprisController.hasPlayers
|
||||||
|
CircularProgress {
|
||||||
|
id: circularProgress
|
||||||
|
visible: MprisController.hasPlayers && MprisController.activePlayer().lengthSupported
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: rowLayout.spacing
|
||||||
|
lineWidth: 2
|
||||||
|
value: MprisController.activePlayer().lengthSupported ? MprisController.activePlayer()?.position / MprisController.activePlayer()?.length : 0
|
||||||
|
implicitSize: 26
|
||||||
|
colSecondary: Appearance.colors.colSecondaryContainer
|
||||||
|
colPrimary: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
enableAnimation: false
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
visible: MprisController.hasPlayers && MprisController.activePlayer().lengthSupported
|
||||||
|
anchors.centerIn: parent
|
||||||
|
fill: 1
|
||||||
|
text: MprisController.activePlayer()?.isPlaying ? "music_note" : "pause"
|
||||||
|
iconSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: Config.options.bar.verbose
|
||||||
|
width: rowLayout.width - (circularProgress.size + rowLayout.spacing * 2)
|
||||||
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: rowLayout.spacing
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: `${MprisController.activePlayer()?.trackTitle}${MprisController.activePlayer()?.trackArtist ? ' • ' + MprisController.activePlayer().trackArtist : ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
bar/NotificationIcon.qml
Normal file
45
bar/NotificationIcon.qml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
height: parent.height
|
||||||
|
implicitWidth: rowLayout.implicitWidth
|
||||||
|
Layout.leftMargin: Appearance.rounding.screenRounding
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.m3colors.m3error
|
||||||
|
text: NotificationServer.amountNotifications
|
||||||
|
visible: {
|
||||||
|
NotificationServer.amountNotifications > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
visible: true
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: NotificationServer.amountNotifications > 0 ? "notifications_unread" : "notifications"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: NotificationServer.amountNotifications > 0 ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
text: "•"
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
bar/SysTray.qml
Normal file
39
bar/SysTray.qml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
height: parent.height
|
||||||
|
implicitWidth: rowLayout.implicitWidth
|
||||||
|
Layout.leftMargin: Appearance.rounding.screenRounding
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: SystemTray.items
|
||||||
|
|
||||||
|
SysTrayItem {
|
||||||
|
required property SystemTrayItem modelData
|
||||||
|
item: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
text: "•"
|
||||||
|
visible: {
|
||||||
|
SystemTray.items.values.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
bar/SysTrayItem.qml
Normal file
52
bar/SysTrayItem.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import qs.common
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property SystemTrayItem item
|
||||||
|
property bool targetMenuOpen: false
|
||||||
|
property int trayItemWidth: Appearance.font.pixelSize.larger
|
||||||
|
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: trayItemWidth
|
||||||
|
onClicked: (event) => {
|
||||||
|
switch (event.button) {
|
||||||
|
case Qt.LeftButton:
|
||||||
|
item.activate();
|
||||||
|
break;
|
||||||
|
case Qt.RightButton:
|
||||||
|
if (item.hasMenu) {
|
||||||
|
menu.open();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QsMenuAnchor {
|
||||||
|
id: menu
|
||||||
|
menu: root.item.menu
|
||||||
|
anchor {
|
||||||
|
item: root // Works instead of using window
|
||||||
|
edges: Edges.Bottom | Edges.Right
|
||||||
|
gravity: Edges.Bottom | Edges.Left
|
||||||
|
margins {
|
||||||
|
top: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: trayIcon
|
||||||
|
source: root.item.icon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
}
|
||||||
|
}
|
||||||
16
bar/Time.qml
Normal file
16
bar/Time.qml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
readonly property string time: {
|
||||||
|
Qt.formatDateTime(clock.date, "ddd, MMM dd hh:mm:ss AP")
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: clock
|
||||||
|
precision: SystemClock.Seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
180
bar/Workspaces.qml
Normal file
180
bar/Workspaces.qml
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls // button
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs.common
|
||||||
|
import qs.common.functions
|
||||||
|
import qs.common.widgets
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property bool borderless: Config.options.bar.borderless
|
||||||
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(QsWindow.window?.screen)
|
||||||
|
readonly property list<HyprlandMonitor> monitors: Hyprland.monitors.values
|
||||||
|
readonly property HyprlandToplevel activeWindow: Hyprland.activeToplevel
|
||||||
|
|
||||||
|
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
|
||||||
|
property list<bool> workspaceOccupied: []
|
||||||
|
|
||||||
|
property int widgetPadding: 4
|
||||||
|
property int workspaceButtonWidth: 26
|
||||||
|
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
|
||||||
|
property real workspaceIconOpacityShrinked: 1
|
||||||
|
property real workspaceIconMarginShrinked: -4
|
||||||
|
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
|
||||||
|
|
||||||
|
function updateWorkspaceOccupied() {
|
||||||
|
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
|
||||||
|
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMonitorWorkspace(id) {
|
||||||
|
for (var i = 0; i < monitors.length; i++){
|
||||||
|
if (id === monitors[i].id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: updateWorkspaceOccupied()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland.workspaces
|
||||||
|
function onValuesChanged() {
|
||||||
|
root.updateWorkspaceOccupied();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
|
||||||
|
implicitHeight: Appearance.sizes.barHeight
|
||||||
|
// Workspaces - background
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
z: 1
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitHeight: Appearance.sizes.barHeight
|
||||||
|
|
||||||
|
// https://doc.qt.io/qt-6/qml-qtquick-repeater.html
|
||||||
|
Repeater {
|
||||||
|
model: Config.options.bar.workspaces.shown
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
z: 1
|
||||||
|
implicitHeight: root.workspaceButtonWidth
|
||||||
|
implicitWidth: root.workspaceButtonWidth
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
property var leftOccupied: index - 1 >= 0 && (root.workspaceOccupied[index-1] || root.isMonitorWorkspace(index))
|
||||||
|
property var rightOccupied: index + 1 < workspaceOccupied.length && (root.workspaceOccupied[index+1] || root.isMonitorWorkspace(index+2))
|
||||||
|
|
||||||
|
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
|
||||||
|
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
|
||||||
|
|
||||||
|
topLeftRadius: radiusLeft
|
||||||
|
bottomLeftRadius: radiusLeft
|
||||||
|
topRightRadius: radiusRight
|
||||||
|
bottomRightRadius: radiusRight
|
||||||
|
|
||||||
|
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
|
||||||
|
opacity: (root.workspaceOccupied[index] || root.isMonitorWorkspace(index+1)) ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on radiusLeft {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radiusRight {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active workspace
|
||||||
|
Rectangle {
|
||||||
|
z: 2
|
||||||
|
property real activeWorkspaceMargin: 2
|
||||||
|
implicitHeight: root.workspaceButtonWidth - activeWorkspaceMargin * 2
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
property real idx1: root.workspaceIndexInGroup
|
||||||
|
property real idx2: root.workspaceIndexInGroup
|
||||||
|
x: Math.min(idx1, idx2) * root.workspaceButtonWidth + activeWorkspaceMargin
|
||||||
|
implicitWidth: Math.abs(idx1 - idx2) * root.workspaceButtonWidth + root.workspaceButtonWidth - activeWorkspaceMargin * 2
|
||||||
|
|
||||||
|
Behavior on activeWorkspaceMargin {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on idx1 { // leading animation
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
easing.type: Easing.OutSine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Behavior on idx2 { // following animation
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
easing.type: Easing.OutSine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// workspaces - numbers
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayoutNumbers
|
||||||
|
z: 3
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitHeight: Appearance.sizes.barHeight
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Config.options.bar.workspaces.shown
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: button
|
||||||
|
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
|
||||||
|
Layout.fillHeight: true
|
||||||
|
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
|
||||||
|
width: root.workspaceButtonWidth
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
id: workspaceButtonBackground
|
||||||
|
implicitWidth: root.workspaceButtonWidth
|
||||||
|
implicitHeight: root.workspaceButtonWidth
|
||||||
|
|
||||||
|
StyledText { // Workspace number text
|
||||||
|
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers) ? 1 : 0
|
||||||
|
z: 3
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
|
||||||
|
text: `${button.workspaceValue}`
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
|
||||||
|
Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1Inactive
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
252
common/Appearance.qml
Normal file
252
common/Appearance.qml
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
// Appearance.qml taken from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/Appearance.qml
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.common
|
||||||
|
import qs.common.functions
|
||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property JsonObject m3colors: Colors.options.m3colors
|
||||||
|
property QtObject animation
|
||||||
|
property QtObject animationCurves
|
||||||
|
property QtObject colors
|
||||||
|
property QtObject rounding
|
||||||
|
property QtObject font
|
||||||
|
property QtObject sizes
|
||||||
|
property QtObject theme
|
||||||
|
property string syntaxHighlightingTheme
|
||||||
|
|
||||||
|
// Extremely conservative transparency values for consistency and readability
|
||||||
|
property real transparency: Config.options?.appearance.transparency ? (root.theme.darkmode ? 0.1 : 0.07) : 0
|
||||||
|
property real contentTransparency: Config.options?.appearance.transparency ? (root.theme.darkmode ? 0.55 : 0.55) : 0
|
||||||
|
|
||||||
|
theme: QtObject {
|
||||||
|
property bool darkmode: false
|
||||||
|
property bool transparent: false
|
||||||
|
}
|
||||||
|
|
||||||
|
colors: QtObject {
|
||||||
|
property color colSubtext: m3colors.m3outline
|
||||||
|
property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.transparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
|
||||||
|
property color colOnLayer0: m3colors.m3onBackground
|
||||||
|
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
|
||||||
|
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
|
||||||
|
property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
|
||||||
|
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
|
||||||
|
property color colOnLayer1: m3colors.m3onSurfaceVariant;
|
||||||
|
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
|
||||||
|
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.1), root.contentTransparency)
|
||||||
|
property color colOnLayer2: m3colors.m3onSurface;
|
||||||
|
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
|
||||||
|
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
|
||||||
|
property color colOnLayer3: m3colors.m3onSurface;
|
||||||
|
property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)
|
||||||
|
property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);
|
||||||
|
property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency)
|
||||||
|
property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency);
|
||||||
|
property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency);
|
||||||
|
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
|
||||||
|
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
|
||||||
|
property color colPrimary: m3colors.m3primary
|
||||||
|
property color colOnPrimary: m3colors.m3onPrimary
|
||||||
|
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
|
||||||
|
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
|
||||||
|
property color colPrimaryContainer: m3colors.m3primaryContainer
|
||||||
|
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
|
||||||
|
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
|
||||||
|
property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer
|
||||||
|
property color colSecondary: m3colors.m3secondary
|
||||||
|
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
|
||||||
|
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
|
||||||
|
property color colSecondaryContainer: m3colors.m3secondaryContainer
|
||||||
|
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)
|
||||||
|
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
|
||||||
|
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
|
||||||
|
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
|
||||||
|
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
|
||||||
|
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
|
||||||
|
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
|
||||||
|
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
|
||||||
|
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
|
||||||
|
property color colTooltip: m3colors.m3inverseSurface
|
||||||
|
property color colOnTooltip: m3colors.m3inverseOnSurface
|
||||||
|
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
|
||||||
|
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
|
||||||
|
property color colOutlineVariant: m3colors.m3outlineVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
rounding: QtObject {
|
||||||
|
property int unsharpen: 2
|
||||||
|
property int unsharpenmore: 6
|
||||||
|
property int verysmall: 8
|
||||||
|
property int small: 12
|
||||||
|
property int normal: 17
|
||||||
|
property int large: 23
|
||||||
|
property int verylarge: 30
|
||||||
|
property int full: 9999
|
||||||
|
property int screenRounding: large
|
||||||
|
property int windowRounding: 18
|
||||||
|
}
|
||||||
|
|
||||||
|
font: QtObject {
|
||||||
|
property QtObject family: QtObject {
|
||||||
|
property string main: "Rubik"
|
||||||
|
property string title: "Gabarito"
|
||||||
|
property string iconMaterial: "Material Symbols Rounded"
|
||||||
|
property string iconNerd: "SpaceMono NF"
|
||||||
|
property string monospace: "JetBrains Mono NF"
|
||||||
|
property string reading: "Readex Pro"
|
||||||
|
property string expressive: "Space Grotesk"
|
||||||
|
}
|
||||||
|
property QtObject pixelSize: QtObject {
|
||||||
|
property int smallest: 10
|
||||||
|
property int smaller: 12
|
||||||
|
property int small: 15
|
||||||
|
property int normal: 16
|
||||||
|
property int large: 17
|
||||||
|
property int larger: 19
|
||||||
|
property int huge: 22
|
||||||
|
property int hugeass: 23
|
||||||
|
property int title: huge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animationCurves: QtObject {
|
||||||
|
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
|
||||||
|
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
|
||||||
|
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
|
||||||
|
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
|
||||||
|
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||||
|
readonly property list<real> emphasizedFirstHalf: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82]
|
||||||
|
readonly property list<real> emphasizedLastHalf: [5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||||
|
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||||
|
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||||
|
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
||||||
|
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
||||||
|
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
||||||
|
readonly property real expressiveFastSpatialDuration: 350
|
||||||
|
readonly property real expressiveDefaultSpatialDuration: 500
|
||||||
|
readonly property real expressiveSlowSpatialDuration: 650
|
||||||
|
readonly property real expressiveEffectsDuration: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
animation: QtObject {
|
||||||
|
property QtObject elementMove: QtObject {
|
||||||
|
property int duration: animationCurves.expressiveDefaultSpatialDuration
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
|
||||||
|
property int velocity: 650
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMove.duration
|
||||||
|
easing.type: root.animation.elementMove.type
|
||||||
|
easing.bezierCurve: root.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property Component colorAnimation: Component {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: root.animation.elementMove.duration
|
||||||
|
easing.type: root.animation.elementMove.type
|
||||||
|
easing.bezierCurve: root.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property QtObject elementMoveEnter: QtObject {
|
||||||
|
property int duration: 400
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.emphasizedDecel
|
||||||
|
property int velocity: 650
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMoveEnter.duration
|
||||||
|
easing.type: root.animation.elementMoveEnter.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property QtObject elementMoveExit: QtObject {
|
||||||
|
property int duration: 200
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.emphasizedAccel
|
||||||
|
property int velocity: 650
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMoveExit.duration
|
||||||
|
easing.type: root.animation.elementMoveExit.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveExit.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property QtObject elementMoveFast: QtObject {
|
||||||
|
property int duration: animationCurves.expressiveEffectsDuration
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.expressiveEffects
|
||||||
|
property int velocity: 850
|
||||||
|
property Component colorAnimation: Component {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: root.animation.elementMoveFast.duration
|
||||||
|
easing.type: root.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMoveFast.duration
|
||||||
|
easing.type: root.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject clickBounce: QtObject {
|
||||||
|
property int duration: 200
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.expressiveFastSpatial
|
||||||
|
property int velocity: 850
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.clickBounce.duration
|
||||||
|
easing.type: root.animation.clickBounce.type
|
||||||
|
easing.bezierCurve: root.animation.clickBounce.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property QtObject scroll: QtObject {
|
||||||
|
property int duration: 400
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.standardDecel
|
||||||
|
}
|
||||||
|
property QtObject menuDecel: QtObject {
|
||||||
|
property int duration: 350
|
||||||
|
property int type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes: QtObject {
|
||||||
|
property real baseBarHeight: 40
|
||||||
|
property real barHeight: Config.options.bar.cornerStyle === 1 ?
|
||||||
|
(baseBarHeight + Appearance.sizes.hyprlandGapsOut * 2) : baseBarHeight
|
||||||
|
property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140
|
||||||
|
property real barCenterSideModuleWidthShortened: 280
|
||||||
|
property real barCenterSideModuleWidthHellaShortened: 190
|
||||||
|
property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value
|
||||||
|
property real barHellaShortenScreenWidthThreshold: 1000 // Shorten even more...
|
||||||
|
property real sidebarWidth: 460
|
||||||
|
property real sidebarWidthExtended: 750
|
||||||
|
property real osdWidth: 200
|
||||||
|
property real mediaControlsWidth: 440
|
||||||
|
property real mediaControlsHeight: 160
|
||||||
|
property real notificationPopupWidth: 410
|
||||||
|
property real searchWidthCollapsed: 260
|
||||||
|
property real searchWidth: 450
|
||||||
|
property real hyprlandGapsOut: 5
|
||||||
|
property real elevationMargin: 10
|
||||||
|
property real fabShadowRadius: 5
|
||||||
|
property real fabHoveredShadowRadius: 7
|
||||||
|
}
|
||||||
|
|
||||||
|
syntaxHighlightingTheme: Appearance.theme.darkmode ? "Monokai" : "ayu Light"
|
||||||
|
}
|
||||||
90
common/Colors.qml
Normal file
90
common/Colors.qml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property string filePath: Directories.shellColorConfigPath
|
||||||
|
property alias options: colorConfigOptionsJsonAdpater
|
||||||
|
property bool ready: false
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: root.filePath
|
||||||
|
watchChanges: true
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoaded: root.ready = true
|
||||||
|
onLoadFailed: error => {
|
||||||
|
if (error == FileViewError.FileNotFound) {
|
||||||
|
writeAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: colorConfigOptionsJsonAdpater
|
||||||
|
|
||||||
|
property JsonObject m3colors: JsonObject {
|
||||||
|
property color m3primary_paletteKeyColor: "#a3c9ff"
|
||||||
|
property color m3secondary_paletteKeyColor: "#b2c8ea"
|
||||||
|
property color m3tertiary_paletteKeyColor: "#ffb783"
|
||||||
|
property color m3neutral_paletteKeyColor: "#c7c6c6"
|
||||||
|
property color m3neutral_variant_paletteKeyColor: "#cbc4ce"
|
||||||
|
property color m3background: "#111418"
|
||||||
|
property color m3onBackground: "#e1e2e9"
|
||||||
|
property color m3surface: "#111318"
|
||||||
|
property color m3surfaceDim: "#111318"
|
||||||
|
property color m3surfaceBright: "#37393e"
|
||||||
|
property color m3surfaceContainerLowest: "#0c0e13"
|
||||||
|
property color m3surfaceContainerLow: "#191c20"
|
||||||
|
property color m3surfaceContainer: "#1d2024"
|
||||||
|
property color m3surfaceContainerHigh: "#272a2f"
|
||||||
|
property color m3surfaceContainerHighest: "#32353a"
|
||||||
|
property color m3onSurface: "#e1e2e9"
|
||||||
|
property color m3surfaceVariant: "#414751"
|
||||||
|
property color m3onSurfaceVariant: "#c1c7d2"
|
||||||
|
property color m3inverseSurface: "#e1e2e9"
|
||||||
|
property color m3inverseOnSurface: "#2e3036"
|
||||||
|
property color m3outline: "#8b919c"
|
||||||
|
property color m3outlineVariant: "#414751"
|
||||||
|
property color m3shadow: "#000000"
|
||||||
|
property color m3scrim: "#000000"
|
||||||
|
property color m3surfaceTint: "#e0e0e1"
|
||||||
|
property color m3primary: "#a3c9ff"
|
||||||
|
property color m3onPrimary: "#00315c"
|
||||||
|
property color m3primaryContainer: "#1563ab"
|
||||||
|
property color m3onPrimaryContainer: "#ffffff"
|
||||||
|
property color m3inversePrimary: "#0e60a8"
|
||||||
|
property color m3secondary: "#b2c8ea"
|
||||||
|
property color m3onSecondary: "#1b314c"
|
||||||
|
property color m3secondaryContainer: "#354a66"
|
||||||
|
property color m3onSecondaryContainer: "#d6e5ff"
|
||||||
|
property color m3tertiary: "#ffb783"
|
||||||
|
property color m3onTertiary: "#4f2500"
|
||||||
|
property color m3tertiaryContainer: "#984d00"
|
||||||
|
property color m3onTertiaryContainer: "#ffffff"
|
||||||
|
property color m3error: "#ffb4ab"
|
||||||
|
property color m3onError: "#e1e2e9"
|
||||||
|
property color m3errorContainer: "#93000a"
|
||||||
|
property color m3onErrorContainer: "#ffdad6"
|
||||||
|
property color m3primaryFixed: "#d3e3ff"
|
||||||
|
property color m3primaryFixedDim: "#a3c9ff"
|
||||||
|
property color m3onPrimaryFixed: "#001c39"
|
||||||
|
property color m3onPrimaryFixedVariant: "#004882"
|
||||||
|
property color m3secondaryFixed: "#d3e3ff"
|
||||||
|
property color m3secondaryFixedDim: "#b2c8ea"
|
||||||
|
property color m3onSecondaryFixed: "#031c36"
|
||||||
|
property color m3onSecondaryFixedVariant: "#334864"
|
||||||
|
property color m3tertiaryFixed: "#ffdcc5"
|
||||||
|
property color m3tertiaryFixedDim: "#ffb783"
|
||||||
|
property color m3onTertiaryFixed: "#301400"
|
||||||
|
property color m3onTertiaryFixedVariant: "#703700"
|
||||||
|
property color m3success: "#cee8dd"
|
||||||
|
property color m3onSuccess: "#b1cdc1"
|
||||||
|
property color m3successContainer: "#b2ccc2"
|
||||||
|
property color m3onSuccessContainer: "#ffffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
common/Config.qml
Normal file
92
common/Config.qml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property string filePath: Directories.shellConfigPath
|
||||||
|
property alias options: configOptionsJsonAdpater
|
||||||
|
property bool ready: false
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: root.filePath
|
||||||
|
watchChanges: true
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoaded: root.ready = true
|
||||||
|
onLoadFailed: error => {
|
||||||
|
if (error == FileViewError.FileNotFound) {
|
||||||
|
writeAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: configOptionsJsonAdpater
|
||||||
|
|
||||||
|
property JsonObject appearance: JsonObject {
|
||||||
|
property bool extraBackgroundTint: true
|
||||||
|
property int fakeScreenRounding: 2
|
||||||
|
property bool transparency: false
|
||||||
|
property JsonObject wallpaperTheming: JsonObject {
|
||||||
|
property bool enableAppsAndShell: true
|
||||||
|
property bool enableQtApps: true
|
||||||
|
property bool enableTerminal: true
|
||||||
|
}
|
||||||
|
property JsonObject palette: JsonObject {
|
||||||
|
property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property JsonObject background: JsonObject {
|
||||||
|
property string wallpaperPath: ""
|
||||||
|
property string thumbnailPath: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
property JsonObject bar: JsonObject {
|
||||||
|
property bool bottom: false // Instead of top
|
||||||
|
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle
|
||||||
|
property bool borderless: false // true for no grouping of items
|
||||||
|
property string topLeftIcon: "spark" // Options: distro, spark
|
||||||
|
property bool showBackground: true
|
||||||
|
property bool verbose: true
|
||||||
|
property JsonObject resources: JsonObject {
|
||||||
|
property bool alwaysShowSwap: true
|
||||||
|
property bool alwaysShowCpu: false
|
||||||
|
}
|
||||||
|
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
|
||||||
|
property JsonObject utilButtons: JsonObject {
|
||||||
|
property bool showScreenSnip: true
|
||||||
|
property bool showColorPicker: false
|
||||||
|
property bool showMicToggle: false
|
||||||
|
property bool showKeyboardToggle: true
|
||||||
|
property bool showDarkModeToggle: true
|
||||||
|
property bool showPerformanceProfileToggle: false
|
||||||
|
}
|
||||||
|
property JsonObject workspaces: JsonObject {
|
||||||
|
property bool monochromeIcons: true
|
||||||
|
property int shown: 10
|
||||||
|
property bool showAppIcons: true
|
||||||
|
property bool alwaysShowNumbers: false
|
||||||
|
property int showNumberDelay: 300 // milliseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property JsonObject osd: JsonObject {
|
||||||
|
property int timeout: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
property JsonObject time: JsonObject {
|
||||||
|
// https://doc.qt.io/qt-6/qtime.html#toString
|
||||||
|
property string format: "hh:mm"
|
||||||
|
property string dateFormat: "ddd, dd/MM"
|
||||||
|
}
|
||||||
|
|
||||||
|
property JsonObject windows: JsonObject {
|
||||||
|
property bool showTitlebar: true // Client-side decoration for shell apps
|
||||||
|
property bool centerTitle: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
common/Directories.qml
Normal file
30
common/Directories.qml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Qt.labs.platform // StandardPaths
|
||||||
|
import qs.common.functions
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
// XDG Directories prefixed with file://
|
||||||
|
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
|
||||||
|
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
|
||||||
|
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
|
||||||
|
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
||||||
|
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
|
||||||
|
|
||||||
|
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/hydro-os`)
|
||||||
|
property string shellConfigName: "config.json"
|
||||||
|
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
|
||||||
|
|
||||||
|
property string shellColorConfigName: "color.json"
|
||||||
|
property string shellColorConfigPath: `${Directories.shellConfig}/${Directories.shellColorConfigName}`
|
||||||
|
|
||||||
|
property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`)
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
|
||||||
|
Quickshell.execDetached(["mkdir", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
|
||||||
|
}
|
||||||
|
}
|
||||||
66
common/MprisController.qml
Normal file
66
common/MprisController.qml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
readonly property list<MprisPlayer> activePlayers: Mpris.players.values
|
||||||
|
readonly property var meaningfulPlayers: filterDuplicatePlayers(activePlayers)
|
||||||
|
readonly property bool hasPlayers: meaningfulPlayers.length > 0
|
||||||
|
property int playerIndex: 0
|
||||||
|
|
||||||
|
function activePlayer() {
|
||||||
|
if (!hasPlayers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
assertIndex();
|
||||||
|
return meaningfulPlayers[playerIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
function shiftPlayer(shift) {
|
||||||
|
playerIndex = (playerIndex + shift + activePlayers.length) % activePlayers.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIndex() {
|
||||||
|
if (playerIndex < 0 || playerIndex >= meaningfulPlayers.length) {
|
||||||
|
playerIndex = (playerIndex + activePlayers.length) % activePlayers.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterDuplicatePlayers(players) {
|
||||||
|
let filtered = [];
|
||||||
|
let used = new Set();
|
||||||
|
|
||||||
|
for (let i = 0; i < players.length; ++i) {
|
||||||
|
if (used.has(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let p1 = players[i];
|
||||||
|
let group = [i];
|
||||||
|
|
||||||
|
// find duplicates
|
||||||
|
for (let j = i + 1; j < players.length; ++j) {
|
||||||
|
let p2 = players[j];
|
||||||
|
if (p1.trackTitle && p2.trackTitle && (
|
||||||
|
p1.trackTitle.includes(p2.trackTitle) ||
|
||||||
|
p2.trackTitle.includes(p1.trackTitle) ||
|
||||||
|
(p1.position - p2.position <= 2 && p1.length - p2.length <= 2)
|
||||||
|
)) {
|
||||||
|
group.push(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick with non-empty trackArtUrl
|
||||||
|
|
||||||
|
let chosenIdx = group.find(idx => players[idx].trackArtUrl && players[idx].trackArtUrl.length > 0);
|
||||||
|
if (chosenIdx === undefined) {
|
||||||
|
chosenIdx = group[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered.push(players[chosenIdx]);
|
||||||
|
group.forEach(idx => used.add(idx));
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
common/NotificationServer.qml
Normal file
9
common/NotificationServer.qml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
readonly property list<Notification> notifications: NotificationServer.trackedNotifications.values
|
||||||
|
readonly property int amountNotifications: notifications.length
|
||||||
|
}
|
||||||
115
common/functions/ColorUtils.qml
Normal file
115
common/functions/ColorUtils.qml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// ColorUtils.qml taken from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/functions/ColorUtils.qml
|
||||||
|
pragma Singleton
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||||
|
* @param {string} color2 - The color to take hue from.
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function colorWithHueOf(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
|
||||||
|
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
|
||||||
|
var hue = c2.hsvHue;
|
||||||
|
var sat = c1.hsvSaturation;
|
||||||
|
var val = c1.hsvValue;
|
||||||
|
var alpha = c1.a;
|
||||||
|
|
||||||
|
return Qt.hsva(hue, sat, val, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||||
|
* @param {string} color2 - The color to take saturation from.
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function colorWithSaturationOf(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
|
||||||
|
var hue = c1.hsvHue;
|
||||||
|
var sat = c2.hsvSaturation;
|
||||||
|
var val = c1.hsvValue;
|
||||||
|
var alpha = c1.a;
|
||||||
|
|
||||||
|
return Qt.hsva(hue, sat, val, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
|
||||||
|
*
|
||||||
|
* @param {string} color - The base color (any Qt.color-compatible string).
|
||||||
|
* @param {number} lightness - The lightness value to use (0-1).
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function colorWithLightness(color, lightness) {
|
||||||
|
var c = Qt.color(color);
|
||||||
|
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||||
|
* @param {string} color2 - The color to take lightness from.
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function colorWithLightnessOf(color1, color2) {
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
return colorWithLightness(color1, c2.hslLightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||||
|
* @param {string} color2 - The accent color.
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function adaptToAccent(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
|
||||||
|
var hue = c2.hslHue;
|
||||||
|
var sat = c2.hslSaturation;
|
||||||
|
var light = c1.hslLightness;
|
||||||
|
var alpha = c1.a;
|
||||||
|
|
||||||
|
return Qt.hsla(hue, sat, light, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixes two colors by a given percentage.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The first color (any Qt.color-compatible string).
|
||||||
|
* @param {string} color2 - The second color.
|
||||||
|
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
|
||||||
|
* @returns {Qt.rgba} The resulting mixed color.
|
||||||
|
*/
|
||||||
|
function mix(color1, color2, percentage = 0.5) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transparentizes a color by a given percentage.
|
||||||
|
*
|
||||||
|
* @param {string} color - The color (any Qt.color-compatible string).
|
||||||
|
* @param {number} percentage - The amount to transparentize (0-1).
|
||||||
|
* @returns {Qt.rgba} The resulting color.
|
||||||
|
*/
|
||||||
|
function transparentize(color, percentage = 1) {
|
||||||
|
var c = Qt.color(color);
|
||||||
|
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
|
||||||
|
}
|
||||||
|
}
|
||||||
17
common/functions/FileUtils.qml
Normal file
17
common/functions/FileUtils.qml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// FileUtiils.qml
|
||||||
|
// taken from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/functions/FileUtils.qml
|
||||||
|
pragma Singleton
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trims the File protocol off the input string
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function trimFileProtocol(str) {
|
||||||
|
return str.startsWith("file://") ? str.slice(7) : str;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
common/widgets/CircularProgress.qml
Normal file
84
common/widgets/CircularProgress.qml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// CircularProgress.qml taken from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/widgets/CircularProgress.qml
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.common
|
||||||
|
|
||||||
|
// Material 3 circular progress. https://m3.material.io/components/progress-indicators/specs
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int implicitSize: 30
|
||||||
|
property int lineWidth: 2
|
||||||
|
property real value: 0
|
||||||
|
property color colPrimary: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
property color colSecondary: Appearance.m3colors.colSecondaryContainer
|
||||||
|
property real gapAngle: 360 / 18
|
||||||
|
property bool fill: false
|
||||||
|
property bool enableAnimation: true
|
||||||
|
property int animationDuration: 800
|
||||||
|
property var easingType: Easing.OutCubic
|
||||||
|
|
||||||
|
implicitHeight: implicitSize
|
||||||
|
implicitWidth: implicitSize
|
||||||
|
|
||||||
|
property real degree: value * 360
|
||||||
|
property real centerX: root.width / 2
|
||||||
|
property real centerY: root.height / 2
|
||||||
|
property real arcRadius: root.implicitSize / 2 - root.lineWidth
|
||||||
|
property real startAngle: -90
|
||||||
|
|
||||||
|
Behavior on degree {
|
||||||
|
enabled: root.enableAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: root.easingType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: root.fill
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
sourceComponent: Rectangle {
|
||||||
|
radius: 9999
|
||||||
|
color: root.colSecondary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
layer.smooth: true
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
ShapePath {
|
||||||
|
id: secondaryPath
|
||||||
|
strokeColor: root.colSecondary
|
||||||
|
strokeWidth: root.lineWidth
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
fillColor: "transparent"
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: root.centerX
|
||||||
|
centerY: root.centerY
|
||||||
|
radiusX: root.arcRadius
|
||||||
|
radiusY: root.arcRadius
|
||||||
|
startAngle: root.startAngle - root.gapAngle
|
||||||
|
sweepAngle: -(360 - root.degree - 2 * root.gapAngle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShapePath {
|
||||||
|
id: primaryPath
|
||||||
|
strokeColor: root.colPrimary
|
||||||
|
strokeWidth: root.lineWidth
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
fillColor: "transparent"
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: root.centerX
|
||||||
|
centerY: root.centerY
|
||||||
|
radiusX: root.arcRadius
|
||||||
|
radiusY: root.arcRadius
|
||||||
|
startAngle: root.startAngle
|
||||||
|
sweepAngle: root.degree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
common/widgets/ContentPage.qml
Normal file
29
common/widgets/ContentPage.qml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// ContentPage.qml from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/widgets/ContentPage.qml
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
StyledFlickable {
|
||||||
|
id: root
|
||||||
|
property real baseWidth: 550
|
||||||
|
property bool forceWidth: false
|
||||||
|
property real bottomContentPadding: 100
|
||||||
|
|
||||||
|
default property alias data: contentColumn.data
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
contentHeight: contentColumn.implicitHeight + root.bottomContentPadding // Add some padding at the bottom
|
||||||
|
implicitWidth: contentColumn.implicitWidth
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentColumn
|
||||||
|
width: root.forceWidth ? root.baseWidth : Math.max(root.baseWidth, implicitWidth)
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
margins: 10
|
||||||
|
}
|
||||||
|
spacing: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
common/widgets/FloatingActionButton.qml
Normal file
60
common/widgets/FloatingActionButton.qml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// FloatingActionButton.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/FloatingActionButton.qml
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material 3 FAB.
|
||||||
|
*/
|
||||||
|
RippleButton {
|
||||||
|
id: root
|
||||||
|
property string iconText: "add"
|
||||||
|
property bool expanded: false
|
||||||
|
property real baseSize: 56
|
||||||
|
property real elementSpacing: 5
|
||||||
|
implicitWidth: Math.max(contentRowLayout.implicitWidth + 10 * 2, baseSize)
|
||||||
|
implicitHeight: baseSize
|
||||||
|
buttonRadius: Appearance.rounding.small
|
||||||
|
colBackground: Appearance.colors.colPrimaryContainer
|
||||||
|
colBackgroundHover: Appearance.colors.colPrimaryContainerHover
|
||||||
|
colRipple: Appearance.colors.colPrimaryContainerActive
|
||||||
|
contentItem: RowLayout {
|
||||||
|
id: contentRowLayout
|
||||||
|
property real horizontalMargins: (root.baseSize - icon.width) / 2
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent?.verticalCenter
|
||||||
|
left: parent?.left
|
||||||
|
leftMargin: contentRowLayout.horizontalMargins
|
||||||
|
}
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
id: icon
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
iconSize: 24
|
||||||
|
color: Appearance.colors.colOnPrimaryContainer
|
||||||
|
text: root.iconText
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
active: true
|
||||||
|
sourceComponent: Revealer {
|
||||||
|
visible: root.expanded || implicitWidth > 0
|
||||||
|
reveal: root.expanded
|
||||||
|
implicitWidth: reveal ? (buttonText.implicitWidth + root.elementSpacing + contentRowLayout.horizontalMargins) : 0
|
||||||
|
StyledText {
|
||||||
|
id: buttonText
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: root.elementSpacing
|
||||||
|
}
|
||||||
|
text: root.buttonText
|
||||||
|
color: Appearance.colors.colOnPrimaryContainer
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.weight: 450
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
common/widgets/MaterialSymbol.qml
Normal file
23
common/widgets/MaterialSymbol.qml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// MaterialSymbol.qml taken from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/widgets/MaterialSymbol.qml
|
||||||
|
import qs.common
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: root
|
||||||
|
property real iconSize: Appearance?.font.pixelSize.small ?? 16
|
||||||
|
property real fill: 0
|
||||||
|
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
font {
|
||||||
|
hintingPreference: Font.PreferFullHinting
|
||||||
|
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
|
||||||
|
pixelSize: iconSize
|
||||||
|
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
|
||||||
|
variableAxes: {
|
||||||
|
"FILL": truncatedFill,
|
||||||
|
"opsz": iconSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
color: Appearance.m3colors.m3onBackground
|
||||||
|
}
|
||||||
9
common/widgets/NavigationRail.qml
Normal file
9
common/widgets/NavigationRail.qml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
// Window content with navigation rail and content pane
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
property bool expanded: true
|
||||||
|
property int currentIndex: 0
|
||||||
|
spacing: 5
|
||||||
|
}
|
||||||
149
common/widgets/NavigationRailButton.qml
Normal file
149
common/widgets/NavigationRailButton.qml
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// NavigationRailButton.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/NavigationRailButton.qml
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
import qs.common.functions
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool toggled: TabBar.tabBar.currentIndex === TabBar.index
|
||||||
|
property string buttonIcon
|
||||||
|
property string buttonText
|
||||||
|
property bool expanded: false
|
||||||
|
property bool showToggledHighlight: true
|
||||||
|
readonly property real visualWidth: root.expanded ? root.baseSize + 20 + itemText.implicitWidth : root.baseSize
|
||||||
|
|
||||||
|
property real baseSize: 56
|
||||||
|
property real baseHighlightHeight: 32
|
||||||
|
property real highlightCollapsedTopMargin: 8
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
// The navigation item’s target area always spans the full width of the
|
||||||
|
// nav rail, even if the item container hugs its contents.
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// implicitWidth: contentItem.implicitWidth
|
||||||
|
implicitHeight: baseSize
|
||||||
|
|
||||||
|
background: null
|
||||||
|
PointingHandInteraction {}
|
||||||
|
|
||||||
|
// Real stuff
|
||||||
|
contentItem: Item {
|
||||||
|
id: buttonContent
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
left: parent.left
|
||||||
|
right: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root.visualWidth
|
||||||
|
implicitHeight: root.expanded ? itemIconBackground.implicitHeight : itemIconBackground.implicitHeight + itemText.implicitHeight
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: itemBackground
|
||||||
|
anchors.top: itemIconBackground.top
|
||||||
|
anchors.left: itemIconBackground.left
|
||||||
|
anchors.bottom: itemIconBackground.bottom
|
||||||
|
implicitWidth: root.visualWidth
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: toggled ?
|
||||||
|
root.showToggledHighlight ?
|
||||||
|
(root.down ? Appearance.colors.colSecondaryContainerActive : root.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer)
|
||||||
|
: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer) :
|
||||||
|
(root.down ? Appearance.colors.colLayer1Active : root.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "expanded"
|
||||||
|
when: root.expanded
|
||||||
|
AnchorChanges {
|
||||||
|
target: itemBackground
|
||||||
|
anchors.top: buttonContent.top
|
||||||
|
anchors.left: buttonContent.left
|
||||||
|
anchors.bottom: buttonContent.bottom
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: itemBackground
|
||||||
|
implicitWidth: root.visualWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transitions: Transition {
|
||||||
|
AnchorAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
PropertyAnimation {
|
||||||
|
target: itemBackground
|
||||||
|
property: "implicitWidth"
|
||||||
|
duration: Appearance.animation.elementMove.duration
|
||||||
|
easing.type: Appearance.animation.elementMove.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: itemIconBackground
|
||||||
|
implicitWidth: root.baseSize
|
||||||
|
implicitHeight: root.baseHighlightHeight
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
MaterialSymbol {
|
||||||
|
id: navRailButtonIcon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
iconSize: 24
|
||||||
|
fill: toggled ? 1 : 0
|
||||||
|
font.weight: (toggled || root.hovered) ? Font.DemiBold : Font.Normal
|
||||||
|
text: buttonIcon
|
||||||
|
color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: itemText
|
||||||
|
anchors {
|
||||||
|
top: itemIconBackground.bottom
|
||||||
|
topMargin: 2
|
||||||
|
horizontalCenter: itemIconBackground.horizontalCenter
|
||||||
|
}
|
||||||
|
states: State {
|
||||||
|
name: "expanded"
|
||||||
|
when: root.expanded
|
||||||
|
AnchorChanges {
|
||||||
|
target: itemText
|
||||||
|
anchors {
|
||||||
|
top: undefined
|
||||||
|
horizontalCenter: undefined
|
||||||
|
left: itemIconBackground.right
|
||||||
|
verticalCenter: itemIconBackground.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transitions: Transition {
|
||||||
|
AnchorAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text: buttonText
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
common/widgets/NavigationRailExpandButton.qml
Normal file
31
common/widgets/NavigationRailExpandButton.qml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// NavigationRailExpandButton.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/NavigationRailExpandButton.qml
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: root
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
implicitWidth: 40
|
||||||
|
implicitHeight: 40
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
onClicked: {
|
||||||
|
parent.expanded = !parent.expanded;
|
||||||
|
}
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
|
||||||
|
rotation: root.parent.expanded ? 0 : -180
|
||||||
|
Behavior on rotation {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
id: icon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
iconSize: 24
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: root.parent.expanded ? "menu_open" : "menu"
|
||||||
|
}
|
||||||
|
}
|
||||||
40
common/widgets/NavigationRailTabArray.qml
Normal file
40
common/widgets/NavigationRailTabArray.qml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// NavigationRailTabArray.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/NavigationRailTabArray.qml
|
||||||
|
import qs.common
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property int currentIndex: 0
|
||||||
|
property bool expanded: false
|
||||||
|
default property alias data: tabBarColumn.data
|
||||||
|
implicitHeight: tabBarColumn.implicitHeight
|
||||||
|
implicitWidth: tabBarColumn.implicitWidth
|
||||||
|
Layout.topMargin: 25
|
||||||
|
Rectangle {
|
||||||
|
property real itemHeight: tabBarColumn.children[0].baseSize
|
||||||
|
property real baseHighlightHeight: tabBarColumn.children[0].baseHighlightHeight
|
||||||
|
anchors {
|
||||||
|
top: tabBarColumn.top
|
||||||
|
left: tabBarColumn.left
|
||||||
|
topMargin: itemHeight * root.currentIndex + (root.expanded ? 0 : ((itemHeight - baseHighlightHeight) / 2))
|
||||||
|
}
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Appearance.colors.colSecondaryContainer
|
||||||
|
implicitHeight: root.expanded ? itemHeight : baseHighlightHeight
|
||||||
|
implicitWidth: tabBarColumn.children[root.currentIndex].visualWidth
|
||||||
|
|
||||||
|
Behavior on anchors.topMargin {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animationCurves.expressiveFastSpatialDuration
|
||||||
|
easing.type: Appearance.animation.elementMove.type
|
||||||
|
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
id: tabBarColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
8
common/widgets/PointingHandInteraction.qml
Normal file
8
common/widgets/PointingHandInteraction.qml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// PointingHandInteraction.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/PointingHandInteraction.qml
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onPressed: (mouse) => mouse.accepted = false
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
26
common/widgets/Revealer.qml
Normal file
26
common/widgets/Revealer.qml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Revealer from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/Revealer.qml
|
||||||
|
import qs.common
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreation of GTK revealer. Expects one single child.
|
||||||
|
*/
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property bool reveal
|
||||||
|
property bool vertical: false
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
implicitWidth: (reveal || vertical) ? childrenRect.width : 0
|
||||||
|
implicitHeight: (reveal || !vertical) ? childrenRect.height : 0
|
||||||
|
visible: reveal || (width > 0 && height > 0)
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
enabled: !vertical
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
enabled: vertical
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
201
common/widgets/RippleButton.qml
Normal file
201
common/widgets/RippleButton.qml
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// RippleButton.qml from end-4 https://github.com/end-4/dots-hyprland/blob/eac4ab3e3c249008d9596023f79dbc2d31012600/.config/quickshell/ii/modules/common/widgets/RippleButton.qml
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
import qs.common.functions
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button with ripple effect similar to in Material Design.
|
||||||
|
*/
|
||||||
|
Button {
|
||||||
|
id: root
|
||||||
|
property bool toggled
|
||||||
|
property string buttonText
|
||||||
|
property real buttonRadius: Appearance?.rounding?.small ?? 4
|
||||||
|
property real buttonRadiusPressed: buttonRadius
|
||||||
|
property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
|
||||||
|
property int rippleDuration: 1200
|
||||||
|
property bool rippleEnabled: true
|
||||||
|
property var downAction // When left clicking (down)
|
||||||
|
property var releaseAction // When left clicking (release)
|
||||||
|
property var altAction // When right clicking
|
||||||
|
property var middleClickAction // When middle clicking
|
||||||
|
|
||||||
|
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
|
||||||
|
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
|
||||||
|
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
|
||||||
|
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
|
||||||
|
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
|
||||||
|
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
|
||||||
|
|
||||||
|
property color buttonColor: root.enabled ?
|
||||||
|
(root.toggled ?
|
||||||
|
(root.hovered ? colBackgroundToggledHover : colBackgroundToggled) :
|
||||||
|
(root.hovered ? colBackgroundHover : colBackground)) :
|
||||||
|
colBackground
|
||||||
|
property color rippleColor: root.toggled ? colRippleToggled : colRipple
|
||||||
|
|
||||||
|
function startRipple(x, y) {
|
||||||
|
const stateY = buttonBackground.y;
|
||||||
|
rippleAnim.x = x;
|
||||||
|
rippleAnim.y = y - stateY;
|
||||||
|
|
||||||
|
const dist = (ox,oy) => ox*ox + oy*oy
|
||||||
|
const stateEndY = stateY + buttonBackground.height
|
||||||
|
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
|
||||||
|
|
||||||
|
rippleFadeAnim.complete();
|
||||||
|
rippleAnim.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
component RippleAnim: NumberAnimation {
|
||||||
|
duration: rippleDuration
|
||||||
|
easing.type: Appearance?.animation.elementMoveEnter.type
|
||||||
|
easing.bezierCurve: Appearance?.animationCurves.standardDecel
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onPressed: (event) => {
|
||||||
|
if(event.button === Qt.RightButton) {
|
||||||
|
if (root.altAction) {
|
||||||
|
root.altAction();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(event.button === Qt.MiddleButton) {
|
||||||
|
if (root.middleClickAction) {
|
||||||
|
root.middleClickAction();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.down = true
|
||||||
|
if (root.downAction) {
|
||||||
|
root.downAction();
|
||||||
|
}
|
||||||
|
if (!root.rippleEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {x,y} = event
|
||||||
|
startRipple(x, y)
|
||||||
|
}
|
||||||
|
onReleased: (event) => {
|
||||||
|
root.down = false
|
||||||
|
if (event.button != Qt.LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root.releaseAction) {
|
||||||
|
root.releaseAction();
|
||||||
|
}
|
||||||
|
root.click() // Because the MouseArea already consumed the event
|
||||||
|
if (!root.rippleEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rippleFadeAnim.restart();
|
||||||
|
}
|
||||||
|
onCanceled: (event) => {
|
||||||
|
root.down = false
|
||||||
|
if (!root.rippleEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rippleFadeAnim.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleAnim {
|
||||||
|
id: rippleFadeAnim
|
||||||
|
duration: rippleDuration * 2
|
||||||
|
target: ripple
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: rippleAnim
|
||||||
|
|
||||||
|
property real x
|
||||||
|
property real y
|
||||||
|
property real radius
|
||||||
|
|
||||||
|
PropertyAction {
|
||||||
|
target: ripple
|
||||||
|
property: "x"
|
||||||
|
value: rippleAnim.x
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: ripple
|
||||||
|
property: "y"
|
||||||
|
value: rippleAnim.y
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: ripple
|
||||||
|
property: "opacity"
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
RippleAnim {
|
||||||
|
target: ripple
|
||||||
|
properties: "implicitWidth,implicitHeight"
|
||||||
|
from: 0
|
||||||
|
to: rippleAnim.radius * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
id: buttonBackground
|
||||||
|
radius: root.buttonEffectiveRadius
|
||||||
|
implicitHeight: 50
|
||||||
|
|
||||||
|
color: root.buttonColor
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: buttonBackground.width
|
||||||
|
height: buttonBackground.height
|
||||||
|
radius: root.buttonEffectiveRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: ripple
|
||||||
|
width: ripple.implicitWidth
|
||||||
|
height: ripple.implicitHeight
|
||||||
|
opacity: 0
|
||||||
|
visible: width > 0 && height > 0
|
||||||
|
|
||||||
|
property real implicitWidth: 0
|
||||||
|
property real implicitHeight: 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
RadialGradient {
|
||||||
|
anchors.fill: parent
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: root.rippleColor }
|
||||||
|
GradientStop { position: 0.3; color: root.rippleColor }
|
||||||
|
GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
x: -ripple.width / 2
|
||||||
|
y: -ripple.height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
text: root.buttonText
|
||||||
|
}
|
||||||
|
}
|
||||||
46
common/widgets/StyledFlickable.qml
Normal file
46
common/widgets/StyledFlickable.qml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// StyledFlickable.qml from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml
|
||||||
|
import QtQuick
|
||||||
|
import qs.common
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: root
|
||||||
|
maximumFlickVelocity: 3500
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100
|
||||||
|
property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50
|
||||||
|
property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120
|
||||||
|
property real scrollTargetY: 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
visible: Config?.options.interactions.scrolling.fasterTouchpadScroll
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
onWheel: function(wheelEvent) {
|
||||||
|
const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold;
|
||||||
|
// The angleDelta.y of a touchpad is usually small and continuous,
|
||||||
|
// while that of a mouse wheel is typically in multiples of ±120.
|
||||||
|
var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor;
|
||||||
|
|
||||||
|
const maxY = Math.max(0, root.contentHeight - root.height);
|
||||||
|
const base = scrollAnim.running ? root.scrollTargetY : root.contentY;
|
||||||
|
var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on contentY {
|
||||||
|
NumberAnimation {
|
||||||
|
id: scrollAnim
|
||||||
|
duration: Appearance.animation.scroll.duration
|
||||||
|
easing.type: Appearance.animation.scroll.type
|
||||||
|
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to keep target synced when not animating
|
||||||
|
onContentYChanged: {
|
||||||
|
if (!scrollAnim.running) {
|
||||||
|
root.scrollTargetY = root.contentY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
common/widgets/StyledText.qml
Normal file
16
common/widgets/StyledText.qml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// StyledText.qml from end-4 https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/modules/common/widgets/StyledText.qml
|
||||||
|
import qs.common
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Text {
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font {
|
||||||
|
hintingPreference: Font.PreferFullHinting
|
||||||
|
family: Appearance?.font.family.main ?? "sans-serif"
|
||||||
|
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||||
|
}
|
||||||
|
color: Appearance?.m3colors.m3onBackground ?? "black"
|
||||||
|
linkColor: Appearance?.m3colors.m3primary
|
||||||
|
}
|
||||||
61
common/widgets/StyledToolTip.qml
Normal file
61
common/widgets/StyledToolTip.qml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// StyledToolTip.qml
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
id: root
|
||||||
|
property string content
|
||||||
|
property bool extraVisibleCondition: true
|
||||||
|
property bool alternativeVisibleCondition: false
|
||||||
|
property bool internalVisibleCondition: {
|
||||||
|
const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
verticalPadding: 5
|
||||||
|
horizontalPadding: 10
|
||||||
|
opacity: internalVisibleCondition ? 1 : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
background: null
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
id: contentItemBackground
|
||||||
|
implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding
|
||||||
|
implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: backgroundRectangle
|
||||||
|
anchors.bottom: contentItemBackground.bottom
|
||||||
|
anchors.horizontalCenter: contentItemBackground.horizontalCenter
|
||||||
|
color: Appearance?.colors.colTooltip ?? "#3C4043"
|
||||||
|
radius: Appearance?.rounding.verysmall ?? 7
|
||||||
|
width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0
|
||||||
|
height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: tooltipTextObject
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: content
|
||||||
|
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
|
||||||
|
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
|
||||||
|
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
osd/MediaControls.qml
Normal file
93
osd/MediaControls.qml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.common
|
||||||
|
import qs
|
||||||
|
import Quickshell.Wayland
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
property bool visible: false
|
||||||
|
readonly property MprisPlayer acivePlayer: MprisController.activePlayer()
|
||||||
|
readonly property real osdWidth: Appearance.sizes.osdWidth
|
||||||
|
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
|
||||||
|
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
|
||||||
|
property real contentPadding: 13
|
||||||
|
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
|
||||||
|
property real artRounding: Appearance.rounding.verysmall
|
||||||
|
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: mediaControlsLoader
|
||||||
|
active: GlobalStates.mediaControlsOpen
|
||||||
|
onActiveChanged: {
|
||||||
|
if (!mediaControlsLoader.active & !MprisController.hasPlayers) {
|
||||||
|
GlobalStates.mediaControlsOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: PanelWindow {
|
||||||
|
id: mediaControlsRoot
|
||||||
|
visible: true
|
||||||
|
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
exclusiveZone: 0
|
||||||
|
margins {
|
||||||
|
top: Appearance.sizes.barHeight
|
||||||
|
bottom: Appearance.sizes.barHeight
|
||||||
|
//left: (mediaControlsRoot.screen.width / 2) - (osdWidth / 2) - widgetWidth
|
||||||
|
}
|
||||||
|
implicitWidth: root.widgetWidth
|
||||||
|
implicitHeight: playerColumnLayout.implicitHeight
|
||||||
|
color: "transparent"
|
||||||
|
WlrLayershell.namespace: "quickshell:mediaControls"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: !Config.options.bar.bottom
|
||||||
|
bottom: Config.options.bar.bottom
|
||||||
|
//left: true
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: playerColumnLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: playerColumnLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: -Appearance.sizes.elevationMargin
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ScriptModel {
|
||||||
|
values: MprisController.meaningfulPlayers
|
||||||
|
}
|
||||||
|
delegate: PlayerControl {
|
||||||
|
required property MprisPlayer modelData
|
||||||
|
contentPadding: root.contentPadding
|
||||||
|
popupRounding: root.popupRounding
|
||||||
|
artRounding: root.artRounding
|
||||||
|
player: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "mediaControls"
|
||||||
|
|
||||||
|
function toggle(): void {
|
||||||
|
mediaControlsLoader.active = !mediaControlsLoader.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
mediaControls.loader.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function open(): void {
|
||||||
|
mediaControlsLoader.active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
osd/PlayerControl.qml
Normal file
198
osd/PlayerControl.qml
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
import qs.common
|
||||||
|
import qs.common.functions
|
||||||
|
import qs.common.widgets
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// -- fields --
|
||||||
|
id: playerController
|
||||||
|
required property MprisPlayer player
|
||||||
|
required property real popupRounding
|
||||||
|
required property real contentPadding
|
||||||
|
required property real artRounding
|
||||||
|
property var artUrl: player?.trackArtUrl
|
||||||
|
property string artDownloadLocation: Directories.coverArt
|
||||||
|
property string artFileName: Qt.md5(artUrl) + ".jpg"
|
||||||
|
property string artFilePath: `${artDownloadLocation}/${artFileName}`
|
||||||
|
property color artDominantColor: ColorUtils.mix((colorQuantizer?.colors[0] ?? Appearance.colors.colPrimary), Appearance.colors.colPrimaryContainer, 0.8) || Appearance.m3colors.m3secondaryContainer
|
||||||
|
property bool downloaded: false
|
||||||
|
|
||||||
|
implicitWidth: widgetWidth
|
||||||
|
implicitHeight: widgetHeight
|
||||||
|
|
||||||
|
// colors
|
||||||
|
property bool backgroundIsDark: artDominantColor.hslLightness < 0.5
|
||||||
|
property QtObject blendedColors: QtObject {
|
||||||
|
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, (backgroundIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)
|
||||||
|
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
|
||||||
|
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5)
|
||||||
|
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
|
||||||
|
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnlayer1, artDominantColor, 0.5)
|
||||||
|
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5)
|
||||||
|
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
|
||||||
|
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
|
||||||
|
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.15)
|
||||||
|
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.5)
|
||||||
|
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.5)
|
||||||
|
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
|
||||||
|
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- components --
|
||||||
|
|
||||||
|
component TrackChangeButton: RippleButton {
|
||||||
|
implicitWidth: 24
|
||||||
|
implicitHeight: 24
|
||||||
|
property var iconName
|
||||||
|
colBackground: ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: playerController.player?.playbackState == MprisPlaybackState.Playing
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
playerController.player.positionChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onArtUrlChanged: {
|
||||||
|
if (playerController.artUrl.length == 0) {
|
||||||
|
playerController.artDominantColor = Appearance.m3colors.m3secondaryContainer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
playerController.downloaded = false
|
||||||
|
coverArtDownloader.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: coverArtDownloader
|
||||||
|
property string targetFile: playerController.artUrl
|
||||||
|
command: ["bash", "-c", `[ -f ${playerController.artFilePath} ] || curl -sSL '${targetFile}' -o '${playerController.artFilePath}'`]
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
playerController.downloaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorQuantizer { // From Quickshell
|
||||||
|
id: colorQuantizer
|
||||||
|
source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : ""
|
||||||
|
depth: 0
|
||||||
|
rescaleSize: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.sizes.elevationMargin
|
||||||
|
color: blendedColors.colLayer0
|
||||||
|
radius: root.popupRounding
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: background.width
|
||||||
|
height: background.height
|
||||||
|
radius: background.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Image {
|
||||||
|
id: blurredArt
|
||||||
|
anchors.fill: parent
|
||||||
|
source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : ""
|
||||||
|
sourceSize.width: background.width
|
||||||
|
sourceSize.height: background.height
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
cache: false
|
||||||
|
antialiasing: true
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
source: blurredArt
|
||||||
|
saturation: 0.2
|
||||||
|
blurEnabled: true
|
||||||
|
blurMax: 100
|
||||||
|
blur: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: ColorUtils.transparentize(playerController.blendedColors.colLayer0, 0.3)
|
||||||
|
radius: playerController.popupRounding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: playerController.contentPadding
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: artBackground
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: height
|
||||||
|
radius: playerController.artRounding
|
||||||
|
color:ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: artBackground.width
|
||||||
|
height: artBackground.height
|
||||||
|
radius: artBackground.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: mediaArt
|
||||||
|
property int size: parent.height
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
cache:false
|
||||||
|
antialiasing: true
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
width: size
|
||||||
|
height: size
|
||||||
|
sourceSize.width: size
|
||||||
|
sourceSize.height: size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: trackTitle
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.large
|
||||||
|
color: blendedColors.colOnLayer0
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: playerController.player?.trackTitle || "Untitled"
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
id: trackArtist
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color:blendedColors.colSubtext
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: playerController.player?.trackArtist
|
||||||
|
}
|
||||||
|
Item { // spacing
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
osd/VolumeDisplay.qml
Normal file
104
osd/VolumeDisplay.qml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs
|
||||||
|
import qs.common
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// communicate when the volume display should show or not
|
||||||
|
property bool showVolumeDisplay: false
|
||||||
|
|
||||||
|
// Bind to pipewire's default output node
|
||||||
|
// https://quickshell.org/docs/v0.2.0/types/Quickshell.Services.Pipewire/PwObjectTracker/
|
||||||
|
PwObjectTracker {
|
||||||
|
objects: [ Pipewire.defaultAudioSink ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a connection to when the volume is changed
|
||||||
|
// https://doc.qt.io/qt-6/qml-qtqml-connections.html
|
||||||
|
Connections {
|
||||||
|
target: Pipewire.defaultAudioSink?.audio
|
||||||
|
|
||||||
|
function onVolumeChanged() {
|
||||||
|
GlobalStates.osdVolumeOpen = true;
|
||||||
|
hideTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer after 1 second hide the volume display
|
||||||
|
// https://doc.qt.io/qt-6/qml-qtqml-timer.html
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
interval: Config.options.osd.timeout
|
||||||
|
onTriggered: GlobalStates.osdVolumeOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// loader to create and destroy volume display
|
||||||
|
LazyLoader {
|
||||||
|
active: GlobalStates.osdVolumeOpen
|
||||||
|
|
||||||
|
// according to documentation in Quickshell, PanelWindow is not an uncreatable-type, despite the qmlls language server's warning
|
||||||
|
// I assume that the yelling is because there is a discrepancy between implementation and language server
|
||||||
|
PanelWindow {
|
||||||
|
// it seems you can use {} if you want multiple under a category
|
||||||
|
// the example for that is in Bar.qml
|
||||||
|
anchors.bottom: true
|
||||||
|
// similar discrepancy it seems
|
||||||
|
margins.bottom: screen.height / 5
|
||||||
|
exclusiveZone: 0
|
||||||
|
|
||||||
|
implicitHeight: 50
|
||||||
|
implicitWidth: 400
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// prevents clicking on volume display
|
||||||
|
mask: Region {}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: height / 2
|
||||||
|
color: Appearance?.colors.colLayer1
|
||||||
|
|
||||||
|
// requires QtQuick.Layouts
|
||||||
|
RowLayout {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
leftMargin: 10
|
||||||
|
rightMargin: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
implicitSize: 30
|
||||||
|
// comes from Quickshell.Widgets
|
||||||
|
source: Quickshell.iconPath("audio-volume-high-symbolic")
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
implicitHeight: 20
|
||||||
|
radius: height / 2
|
||||||
|
color: Appearance?.m3colors.m3secondaryContainer
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
|
||||||
|
// What I presume is that the first ? is to check if defaultAudioSink is there, and if not, the ?? marks the returning value in place
|
||||||
|
implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
|
||||||
|
radius: parent.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
settings.qml
Normal file
176
settings.qml
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//@ pragma UseQApplication
|
||||||
|
//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000
|
||||||
|
|
||||||
|
// launched via qs -p ~/.config/quickshell/$qsConfig/settings.qml
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Window
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
id: root
|
||||||
|
property real contentPadding: 8
|
||||||
|
|
||||||
|
property var pages: [
|
||||||
|
{
|
||||||
|
name: "About",
|
||||||
|
icon: "info",
|
||||||
|
component: "settings/About.qml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
property int currentPage: 0
|
||||||
|
|
||||||
|
visible: true
|
||||||
|
onClosing: Qt.quit()
|
||||||
|
title: "hydro-os Settings"
|
||||||
|
|
||||||
|
minimumWidth: 600
|
||||||
|
minimumHeight: 400
|
||||||
|
width: 110
|
||||||
|
height: 750
|
||||||
|
color: Appearance.m3colors.m3background
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: contentPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if (event.modifiers === Qt.Key_PageDown) {
|
||||||
|
root.currentPage = Math.min(root.currentPage + 1, root.pages.length - 1);
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
root.currentPage = Math.max(root.currentPage - 1, 0);
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Tab) {
|
||||||
|
root.currentPage = (root.currentPage + 1) % root.pages.length;
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_BackTab ) {
|
||||||
|
root.currentPage = (root.currentPage - 1 + root.pages.length) % root.pages.length;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window content with navigation rail and content pane
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: contentPadding
|
||||||
|
Item {
|
||||||
|
id: navRailWrapper
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.margins: 5
|
||||||
|
implicitWidth: navRail.expanded ? 150 : fab.baseSize
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
NavigationRail {
|
||||||
|
id: navRail
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
spacing: 10
|
||||||
|
expanded: root.width > 900
|
||||||
|
|
||||||
|
NavigationRailExpandButton {
|
||||||
|
focus: root.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingActionButton {
|
||||||
|
id: fab
|
||||||
|
iconText: "edit"
|
||||||
|
buttonText: "Edit config"
|
||||||
|
expanded: navRail.expanded
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(`${Directories.config}/hydro-os/config.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledToolTip {
|
||||||
|
extraVisibleCondition: !navRail.expanded
|
||||||
|
content: "Edit shell config file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationRailTabArray {
|
||||||
|
currentIndex: root.currentPage
|
||||||
|
expanded: navRail.expanded
|
||||||
|
Repeater {
|
||||||
|
model: root.pages
|
||||||
|
NavigationRailButton {
|
||||||
|
required property var index
|
||||||
|
required property var modelData
|
||||||
|
toggled: root.currentPage === index
|
||||||
|
onClicked: root.currentPage = index;
|
||||||
|
expanded: navRail.expanded
|
||||||
|
buttonIcon: modelData.icon
|
||||||
|
buttonText: modelData.name
|
||||||
|
showToggledHighlight: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Appearance.m3colors.m3surfaceContainerLow
|
||||||
|
radius: Appearance.rounding.windowRounding - root.contentPadding
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: pageLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity: 1.0
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onCurrentPageChanged() {
|
||||||
|
if (pageLoader.sourceComponent !== root.pages[root.currentPage].component) {
|
||||||
|
switchAnim.complete();
|
||||||
|
switchAnim.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: switchAnim
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: pageLoader
|
||||||
|
properties: "opacity"
|
||||||
|
from: 1
|
||||||
|
to: 0
|
||||||
|
duration: 100
|
||||||
|
easing.type: Appearance.animation.elementMoveExit.type
|
||||||
|
easing.bezierCurve: Appearance.animationCurves.emphasizedFirstHalf
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: pageLoader
|
||||||
|
property: "source"
|
||||||
|
value: root.pages[root.currentPage].component
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: pageLoader
|
||||||
|
properties: "opacity"
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: 200
|
||||||
|
easing.type: Appearance.animation.elementMoveEnter.type
|
||||||
|
easing.bezierCurve: Appearance.animationCurves.emphasizedFirstHalf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
settings/About.qml
Normal file
81
settings/About.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.common
|
||||||
|
import qs.common.widgets
|
||||||
|
|
||||||
|
ContentPage {
|
||||||
|
forceWidth: true
|
||||||
|
|
||||||
|
ContentSection {
|
||||||
|
title: "Distro"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 20
|
||||||
|
Layout.topMargin: 10
|
||||||
|
Layout.bottomMargin: 10
|
||||||
|
IconImage {
|
||||||
|
implicitSize: 80
|
||||||
|
source: Quickshell.iconPath(SystemInfo.logo)
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
// spacing: 10
|
||||||
|
StyledText {
|
||||||
|
text: SystemInfo.distroName
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.title
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
text: SystemInfo.homeUrl
|
||||||
|
textFormat: Text.MarkdownText
|
||||||
|
onLinkActivated: (link) => {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
PointingHandLinkHover {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
RippleButtonWithIcon {
|
||||||
|
materialIcon: "auto_stories"
|
||||||
|
mainText: Translation.tr("Documentation")
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(SystemInfo.documentationUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RippleButtonWithIcon {
|
||||||
|
materialIcon: "support"
|
||||||
|
mainText: Translation.tr("Help & Support")
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(SystemInfo.supportUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RippleButtonWithIcon {
|
||||||
|
materialIcon: "bug_report"
|
||||||
|
mainText: Translation.tr("Report a Bug")
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(SystemInfo.bugReportUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RippleButtonWithIcon {
|
||||||
|
materialIcon: "policy"
|
||||||
|
materialIconFill: false
|
||||||
|
mainText: Translation.tr("Privacy Policy")
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(SystemInfo.privacyPolicyUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user