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