forked from Shinonome/dots-hyprland
Merge branch 'end-4:main' into main
This commit is contained in:
@@ -23,3 +23,5 @@ exec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsCo
|
||||
# Cursor
|
||||
exec-once = hyprctl setcursor Bibata-Modern-Classic 24
|
||||
|
||||
# Fix dock pinned apps not launching properly (https://github.com/end-4/dots-hyprland/issues/2200)
|
||||
exec-once = sleep 3.5 && hyprctl reload && sleep 0.5 && touch ~/.config/quickshell/ii/shell.qml
|
||||
|
||||
@@ -14,9 +14,50 @@ Item {
|
||||
property real padding: 4
|
||||
implicitWidth: row.implicitWidth + padding * 2
|
||||
implicitHeight: row.implicitHeight + padding * 2
|
||||
// Excellent symbol explaination and source :
|
||||
// http://xahlee.info/comp/unicode_computing_symbols.html
|
||||
// https://www.nerdfonts.com/cheat-sheet
|
||||
property var macSymbolMap: ({
|
||||
"Ctrl": "",
|
||||
"Alt": "",
|
||||
"Shift": "",
|
||||
"Space": "",
|
||||
"Tab": "↹",
|
||||
"Equal": "",
|
||||
"Minus": "",
|
||||
"Print": "",
|
||||
"BackSpace": "",
|
||||
"Delete": "⌦",
|
||||
"Return": "",
|
||||
"Period": ".",
|
||||
"Escape": "⎋"
|
||||
})
|
||||
property var functionSymbolMap: ({
|
||||
"F1": "",
|
||||
"F2": "",
|
||||
"F3": "",
|
||||
"F4": "",
|
||||
"F5": "",
|
||||
"F6": "",
|
||||
"F7": "",
|
||||
"F8": "",
|
||||
"F9": "",
|
||||
"F10": "",
|
||||
"F11": "",
|
||||
"F12": "",
|
||||
})
|
||||
|
||||
property var mouseSymbolMap: ({
|
||||
"mouse_up": "",
|
||||
"mouse_down": "",
|
||||
"mouse:272": "L",
|
||||
"mouse:273": "R",
|
||||
"Scroll ↑/↓": "",
|
||||
"Page_↑/↓": "⇞/⇟",
|
||||
})
|
||||
|
||||
property var keyBlacklist: ["Super_L"]
|
||||
property var keySubstitutions: ({
|
||||
property var keySubstitutions: Object.assign({
|
||||
"Super": "",
|
||||
"mouse_up": "Scroll ↓", // ikr, weird
|
||||
"mouse_down": "Scroll ↑", // trust me bro
|
||||
@@ -27,7 +68,14 @@ Item {
|
||||
"Hash": "#",
|
||||
"Return": "Enter",
|
||||
// "Shift": "",
|
||||
})
|
||||
},
|
||||
!!Config.options.cheatsheet.superKey ? {
|
||||
"Super": Config.options.cheatsheet.superKey,
|
||||
}: {},
|
||||
Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {},
|
||||
Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {},
|
||||
Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {},
|
||||
)
|
||||
|
||||
Row { // Keybind columns
|
||||
id: row
|
||||
@@ -77,6 +125,17 @@ Item {
|
||||
var result = [];
|
||||
for (var i = 0; i < keybindSection.modelData.keybinds.length; i++) {
|
||||
const keybind = keybindSection.modelData.keybinds[i];
|
||||
|
||||
if (!Config.options.cheatsheet.splitButtons) {
|
||||
|
||||
for (var j = 0; j < keybind.mods.length; j++) {
|
||||
keybind.mods[j] = keySubstitutions[keybind.mods[j]] || keybind.mods[j];
|
||||
}
|
||||
keybind.mods = [keybind.mods.join(' ') ]
|
||||
keybind.mods[0] += !keyBlacklist.includes(keybind.key) && keybind.mods[0].length ? ' ' : ''
|
||||
keybind.mods[0] += !keyBlacklist.includes(keybind.key) ? (keySubstitutions[keybind.key] || keybind.key) : ''
|
||||
}
|
||||
|
||||
result.push({
|
||||
"type": "keys",
|
||||
"mods": keybind.mods,
|
||||
@@ -108,17 +167,19 @@ Item {
|
||||
delegate: KeyboardKey {
|
||||
required property var modelData
|
||||
key: keySubstitutions[modelData] || modelData
|
||||
pixelSize: Config.options.cheatsheet.fontSize.key
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
id: keybindPlus
|
||||
visible: !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0
|
||||
visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0
|
||||
text: "+"
|
||||
}
|
||||
KeyboardKey {
|
||||
id: keybindKey
|
||||
visible: !keyBlacklist.includes(modelData.key)
|
||||
visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key)
|
||||
key: keySubstitutions[modelData.key] || modelData.key
|
||||
pixelSize: Config.options.cheatsheet.fontSize.key
|
||||
color: Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
@@ -134,7 +195,7 @@ Item {
|
||||
StyledText {
|
||||
id: commentText
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller
|
||||
text: modelData.comment
|
||||
}
|
||||
}
|
||||
@@ -152,4 +213,4 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,22 @@ Singleton {
|
||||
property int suspend: 3
|
||||
}
|
||||
|
||||
property JsonObject cheatsheet: JsonObject {
|
||||
// Use a nerdfont to see the icons
|
||||
// 0: | 1: | 2: | 3: | 4:
|
||||
// 5: | 6: | 7: | 8: | 9:
|
||||
// 10: | 11: | 12: | 13: | 14:
|
||||
property string superKey: ""
|
||||
property bool useMacSymbol: false
|
||||
property bool splitButtons: true
|
||||
property bool useMouseSymbol: false
|
||||
property bool useFnSymbol: false
|
||||
property JsonObject fontSize: JsonObject {
|
||||
property int key: Appearance.font.pixelSize.smaller
|
||||
property int comment: Appearance.font.pixelSize.smaller
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject conflictKiller: JsonObject {
|
||||
property bool autoKillNotificationDaemons: false
|
||||
property bool autoKillTrays: false
|
||||
@@ -364,6 +380,7 @@ Singleton {
|
||||
property JsonObject overlay: JsonObject {
|
||||
property bool openingZoomAnimation: true
|
||||
property bool darkenScreen: true
|
||||
property real clickthroughOpacity: 0.7
|
||||
}
|
||||
|
||||
property JsonObject overview: JsonObject {
|
||||
@@ -395,6 +412,7 @@ Singleton {
|
||||
|
||||
property JsonObject resources: JsonObject {
|
||||
property int updateInterval: 3000
|
||||
property int historyLength: 60
|
||||
}
|
||||
|
||||
property JsonObject musicRecognition: JsonObject {
|
||||
|
||||
@@ -80,7 +80,7 @@ Singleton {
|
||||
}
|
||||
|
||||
property JsonObject overlay: JsonObject {
|
||||
property list<string> open: ["crosshair"]
|
||||
property list<string> open: ["crosshair", "recorder", "volumeMixer", "resources"]
|
||||
property JsonObject crosshair: JsonObject {
|
||||
property bool pinned: false
|
||||
property bool clickthrough: true
|
||||
@@ -90,14 +90,28 @@ Singleton {
|
||||
property JsonObject recorder: JsonObject {
|
||||
property bool pinned: false
|
||||
property bool clickthrough: false
|
||||
property real x: 100
|
||||
property real y: 130
|
||||
property real x: 80
|
||||
property real y: 80
|
||||
}
|
||||
property JsonObject resources: JsonObject {
|
||||
property bool pinned: false
|
||||
property bool clickthrough: true
|
||||
property real x: 1500
|
||||
property real y: 770
|
||||
property int tabIndex: 0
|
||||
}
|
||||
property JsonObject volumeMixer: JsonObject {
|
||||
property bool pinned: false
|
||||
property bool clickthrough: false
|
||||
property real x: 100
|
||||
property real y: 320
|
||||
property real x: 80
|
||||
property real y: 280
|
||||
property int tabIndex: 0
|
||||
}
|
||||
property JsonObject fpsLimiter: JsonObject {
|
||||
property bool pinned: false
|
||||
property bool clickthrough: false
|
||||
property real x: 1576
|
||||
property real y: 630
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
|
||||
/*
|
||||
* Simple one value line graph
|
||||
*/
|
||||
Canvas {
|
||||
id: root
|
||||
|
||||
enum Alignment { Left, Right }
|
||||
|
||||
required property list<real> values
|
||||
property int points: values.length
|
||||
property color color: Appearance.colors.colPrimary
|
||||
property real fillOpacity: 0.5
|
||||
property var alignment: Graph.Alignment.Left
|
||||
|
||||
onValuesChanged: root.requestPaint()
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
if (!root.values || root.values.length < 2)
|
||||
return
|
||||
|
||||
var n = root.points
|
||||
var dx = width / (n - 1)
|
||||
ctx.strokeStyle = root.color
|
||||
ctx.fillStyle = ColorUtils.transparentize(root.color, 1 - root.fillOpacity)
|
||||
ctx.lineWidth = 2
|
||||
ctx.beginPath()
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var valueIndex = (root.alignment === Graph.Alignment.Right) ? root.values.length - n + i : i
|
||||
if (valueIndex < 0 || valueIndex >= root.values.length) {
|
||||
continue; // No data for this point
|
||||
}
|
||||
var x = i * dx
|
||||
var norm = root.values[valueIndex] // already in 0-1 range
|
||||
var y = height - norm * height
|
||||
if (valueIndex === 0) {
|
||||
ctx.moveTo(x, height)
|
||||
ctx.lineTo(x, y)
|
||||
} else {
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
}
|
||||
ctx.stroke()
|
||||
ctx.lineTo(width, height)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ Button {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
property bool tabbedTo: root.focus && (focusReason === Qt.TabFocusReason || focusReason === Qt.BacktabFocusReason)
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
topLeftRadius: root.leftRadius
|
||||
@@ -130,6 +130,9 @@ Button {
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
border.width: root.tabbedTo ? 2 : 0
|
||||
border.color: Appearance.colors.colSecondary
|
||||
}
|
||||
|
||||
contentItem: StyledText {
|
||||
|
||||
@@ -18,5 +18,6 @@ ToolbarButton {
|
||||
iconSize: 22
|
||||
text: iconBtn.text
|
||||
color: iconBtn.colText
|
||||
animateChange: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ Rectangle {
|
||||
property real extraBottomBorderWidth: 2
|
||||
property color borderColor: Appearance.colors.colOnLayer0
|
||||
property real borderRadius: 5
|
||||
property real pixelSize: Appearance.font.pixelSize.smaller
|
||||
property color keyColor: Appearance.m3colors.m3surfaceContainerLow
|
||||
implicitWidth: keyFace.implicitWidth + borderWidth * 2
|
||||
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
|
||||
@@ -35,7 +36,7 @@ Rectangle {
|
||||
id: keyText
|
||||
anchors.centerIn: parent
|
||||
font.family: Appearance.font.family.monospace
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
font.pixelSize: root.pixelSize
|
||||
text: key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
import qs.modules.common.models
|
||||
|
||||
TabBar {
|
||||
id: root
|
||||
property real indicatorPadding: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Item {
|
||||
WheelHandler {
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0) root.incrementCurrentIndex();
|
||||
else if (event.angleDelta.y > 0) root.decrementCurrentIndex();
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: activeIndicator
|
||||
z: 9999
|
||||
anchors.bottom: parent.bottom
|
||||
topLeftRadius: height
|
||||
topRightRadius: height
|
||||
bottomLeftRadius: 0
|
||||
bottomRightRadius: 0
|
||||
color: Appearance.colors.colPrimary
|
||||
// Animation
|
||||
property real baseWidth: root.width / root.count
|
||||
AnimatedTabIndexPair {
|
||||
id: idxPair
|
||||
index: root.currentIndex
|
||||
}
|
||||
height: 3
|
||||
x: Math.min(idxPair.idx1, idxPair.idx2) * baseWidth + root.indicatorPadding
|
||||
width: ((Math.max(idxPair.idx1, idxPair.idx2) + 1) * baseWidth - root.indicatorPadding) - x
|
||||
}
|
||||
|
||||
Rectangle { // Tabbar bottom border
|
||||
id: tabBarBottomBorder
|
||||
z: 9998
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
@@ -10,14 +10,12 @@ TabButton {
|
||||
id: root
|
||||
property string buttonText
|
||||
property string buttonIcon
|
||||
property bool selected: false
|
||||
property int rippleDuration: 1200
|
||||
height: buttonBackground.height
|
||||
property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2
|
||||
|
||||
property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
|
||||
property color colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
property color colRipple: Appearance.colors.colLayer1Active
|
||||
property color colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
|
||||
property color colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, root.checked ? 1 : 0.95)
|
||||
property color colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)
|
||||
|
||||
PointingHandInteraction {}
|
||||
|
||||
@@ -91,8 +89,12 @@ TabButton {
|
||||
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 3
|
||||
}
|
||||
radius: Appearance?.rounding.normal
|
||||
implicitHeight: 37
|
||||
implicitHeight: 42
|
||||
color: (root.hovered ? root.colBackgroundHover : root.colBackground)
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
@@ -156,8 +158,8 @@ TabButton {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: buttonIcon
|
||||
iconSize: Appearance.font.pixelSize.huge
|
||||
fill: selected ? 1 : 0
|
||||
color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1
|
||||
fill: root.checked ? 1 : 0
|
||||
color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
@@ -167,7 +169,7 @@ TabButton {
|
||||
id: buttonTextWidget
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1
|
||||
color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1
|
||||
text: buttonText
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
|
||||
@@ -25,7 +25,7 @@ Scope {
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "quickshell:overlay"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
visible: true
|
||||
color: "transparent"
|
||||
|
||||
@@ -43,6 +43,30 @@ Scope {
|
||||
right: true
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [overlayWindow]
|
||||
active: false
|
||||
onCleared: () => {
|
||||
if (!active) GlobalStates.overlayOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GlobalStates
|
||||
function onOverlayOpenChanged() {
|
||||
delayedGrabTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedGrabTimer
|
||||
interval: Appearance.animation.elementMoveFast.duration
|
||||
onTriggered: {
|
||||
grab.active = GlobalStates.overlayOpen;
|
||||
}
|
||||
}
|
||||
|
||||
OverlayContent {
|
||||
id: overlayContent
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -12,6 +12,7 @@ import qs.modules.overlay.crosshair
|
||||
|
||||
Item {
|
||||
id: root
|
||||
focus: true
|
||||
readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true
|
||||
|
||||
Keys.onPressed: (event) => { // Esc to close
|
||||
|
||||
@@ -6,9 +6,11 @@ Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<var> availableWidgets: [
|
||||
{ identifier: "crosshair", materialSymbol: "point_scan" },
|
||||
{ identifier: "volumeMixer", materialSymbol: "volume_up" },
|
||||
{ identifier: "recorder", materialSymbol: "screen_record" },
|
||||
{ identifier: "volumeMixer", materialSymbol: "volume_up" },
|
||||
{ identifier: "crosshair", materialSymbol: "point_scan" },
|
||||
{ identifier: "fpsLimiter", materialSymbol: "animation" },
|
||||
{ identifier: "resources", materialSymbol: "browse_activity" }
|
||||
]
|
||||
|
||||
readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0
|
||||
|
||||
@@ -8,7 +8,9 @@ import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.modules.overlay.crosshair
|
||||
import qs.modules.overlay.volumeMixer
|
||||
import qs.modules.overlay.fpsLimiter
|
||||
import qs.modules.overlay.recorder
|
||||
import qs.modules.overlay.resources
|
||||
|
||||
DelegateChooser {
|
||||
id: root
|
||||
@@ -16,5 +18,7 @@ DelegateChooser {
|
||||
|
||||
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
|
||||
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
|
||||
DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} }
|
||||
DelegateChoice { roleValue: "recorder"; Recorder {} }
|
||||
DelegateChoice { roleValue: "resources"; Resources {} }
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ AbstractOverlayWidget {
|
||||
id: root
|
||||
|
||||
required property Item contentItem
|
||||
property bool fancyBorders: true
|
||||
property bool showCenterButton: false
|
||||
property bool showClickabilityButton: true
|
||||
|
||||
required property var modelData
|
||||
readonly property string identifier: modelData.identifier
|
||||
@@ -29,6 +32,8 @@ AbstractOverlayWidget {
|
||||
property var persistentStateEntry: Persistent.states.overlay[identifier]
|
||||
property real radius: Appearance.rounding.windowRounding
|
||||
property real minWidth: 250
|
||||
property real padding: 6
|
||||
property real contentRadius: radius - padding
|
||||
|
||||
draggable: GlobalStates.overlayOpen
|
||||
x: Math.round(persistentStateEntry.x) // Round or it'll be blurry
|
||||
@@ -38,9 +43,10 @@ AbstractOverlayWidget {
|
||||
drag {
|
||||
minimumX: 0
|
||||
minimumY: 0
|
||||
maximumX: root.parent.width - root.width
|
||||
maximumY: root.parent.height - root.height
|
||||
maximumX: root.parent?.width - root.width
|
||||
maximumY: root.parent?.height - root.height
|
||||
}
|
||||
opacity: (GlobalStates.overlayOpen || !clickthrough) ? 1.0 : Config.options.overlay.clickthroughOpacity
|
||||
|
||||
// Guarded states & registration funcs
|
||||
property bool open: Persistent.states.overlay.open
|
||||
@@ -96,11 +102,15 @@ AbstractOverlayWidget {
|
||||
Rectangle {
|
||||
id: border
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
color: (root.fancyBorders && GlobalStates.overlayOpen) ? Appearance.colors.colLayer1 : "transparent"
|
||||
radius: root.radius
|
||||
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
|
||||
border.width: 1
|
||||
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
layer.enabled: GlobalStates.overlayOpen
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
@@ -110,25 +120,23 @@ AbstractOverlayWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
z: -1
|
||||
z: root.fancyBorders ? 0 : -1
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Title bar
|
||||
Rectangle {
|
||||
id: titleBar
|
||||
opacity: GlobalStates.overlayOpen ? 1 : 0
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
property real padding: 2
|
||||
Layout.fillWidth: true
|
||||
property real padding: 6
|
||||
implicitWidth: titleBarRow.implicitWidth + padding * 2
|
||||
implicitHeight: titleBarRow.implicitHeight + padding * 2
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1
|
||||
// border.color: Appearance.colors.colOutlineVariant
|
||||
// border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: titleBarRow
|
||||
@@ -136,8 +144,9 @@ AbstractOverlayWidget {
|
||||
fill: parent
|
||||
margins: titleBar.padding
|
||||
leftMargin: titleBar.padding + 8
|
||||
bottomMargin: root.fancyBorders ? 0 : titleBar.padding
|
||||
}
|
||||
spacing: 0
|
||||
spacing: 2
|
||||
|
||||
MaterialSymbol {
|
||||
text: root.materialSymbol
|
||||
@@ -153,6 +162,7 @@ AbstractOverlayWidget {
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
visible: root.showCenterButton
|
||||
materialSymbol: "recenter"
|
||||
onClicked: root.center()
|
||||
StyledToolTip {
|
||||
@@ -161,6 +171,7 @@ AbstractOverlayWidget {
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
visible: (root.pinned && root.showClickabilityButton)
|
||||
materialSymbol: "mouse"
|
||||
toggled: !root.clickthrough
|
||||
onClicked: root.toggleClickthrough()
|
||||
@@ -191,7 +202,10 @@ AbstractOverlayWidget {
|
||||
// Content
|
||||
Item {
|
||||
id: contentContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: root.fancyBorders ? root.padding : 0
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
implicitWidth: root.contentItem.implicitWidth
|
||||
implicitHeight: root.contentItem.implicitHeight
|
||||
children: [root.contentItem]
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.modules.common
|
||||
import qs.modules.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
contentItem: CrosshairContent {}
|
||||
fancyBorders: false // Crosshair should be see-through
|
||||
showCenterButton: true
|
||||
opacity: 1 // The crosshair itself already has transparency if configured
|
||||
showClickabilityButton: false
|
||||
clickthrough: true
|
||||
|
||||
contentItem: CrosshairContent {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.modules.common
|
||||
import qs.modules.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
title: "MangoHud FPS"
|
||||
contentItem: FpsLimiterContent {
|
||||
radius: root.contentRadius
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
enum State { Normal, Success, Error }
|
||||
|
||||
anchors.fill: parent
|
||||
property real padding: 16
|
||||
property var currentState: FpsLimiterContent.State.Normal
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
implicitWidth: content.implicitWidth + (padding * 2)
|
||||
implicitHeight: content.implicitHeight + (padding * 2)
|
||||
|
||||
Timer {
|
||||
id: iconResetTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
root.currentState = FpsLimiterContent.State.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
function applyLimit() {
|
||||
var fpsValue = parseInt(fpsField.text);
|
||||
if (isNaN(fpsValue) || fpsValue < 0) {
|
||||
root.currentState = FpsLimiterContent.State.Error;
|
||||
iconResetTimer.restart();
|
||||
fpsField.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
var cfgPaths = [
|
||||
"~/.config/MangoHud/MangoHud.conf",
|
||||
]; // MangoHud config files
|
||||
|
||||
var updateCommands = cfgPaths.map(path => {
|
||||
return "if grep -q '^fps_limit=' " + path + "; " +
|
||||
"then sed -i 's/^fps_limit=.*/fps_limit=" + fpsValue + "/' " + path + "; " +
|
||||
"else echo 'fps_limit=" + fpsValue + "' >> " + path + "; fi";
|
||||
}).join("; ");
|
||||
|
||||
var cmd = updateCommands + "; pkill -SIGUSR2 mangohud";
|
||||
|
||||
fpsSetter.command = ["bash", "-c", cmd];
|
||||
fpsSetter.startDetached();
|
||||
|
||||
root.currentState = FpsLimiterContent.State.Success;
|
||||
iconResetTimer.restart();
|
||||
|
||||
// Clear the field after applying
|
||||
fpsField.text = "";
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fpsSetter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: content
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
ToolbarTextField {
|
||||
id: fpsField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 200
|
||||
placeholderText: root.currentState === FpsLimiterContent.State.Error ? Translation.tr("Enter a valid number") : Translation.tr("Set FPS limit")
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
focus: true
|
||||
|
||||
onAccepted: {
|
||||
root.applyLimit();
|
||||
}
|
||||
}
|
||||
|
||||
IconToolbarButton {
|
||||
id: applyButton
|
||||
text: switch (root.currentState) {
|
||||
case FpsLimiterContent.State.Error: return "close";
|
||||
case FpsLimiterContent.State.Success: return "check";
|
||||
case FpsLimiterContent.State.Normal:
|
||||
default: return "save";
|
||||
}
|
||||
enabled: root.currentState === FpsLimiterContent.State.Normal && fpsField.text.length > 0
|
||||
onClicked: {
|
||||
root.applyLimit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
@@ -14,6 +13,7 @@ StyledOverlayWidget {
|
||||
contentItem: Rectangle {
|
||||
id: contentItem
|
||||
anchors.centerIn: parent
|
||||
radius: root.contentRadius
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
property real padding: 8
|
||||
implicitHeight: contentColumn.implicitHeight + padding * 2
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Qt.labs.synchronizer
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
property list<var> resources: [
|
||||
{
|
||||
"icon": "planner_review",
|
||||
"name": Translation.tr("CPU"),
|
||||
"history": ResourceUsage.cpuUsageHistory,
|
||||
"maxAvailableString": ResourceUsage.maxAvailableCpuString
|
||||
},
|
||||
{
|
||||
"icon": "memory",
|
||||
"name": Translation.tr("RAM"),
|
||||
"history": ResourceUsage.memoryUsageHistory,
|
||||
"maxAvailableString": ResourceUsage.maxAvailableMemoryString
|
||||
},
|
||||
{
|
||||
"icon": "swap_horiz",
|
||||
"name": Translation.tr("Swap"),
|
||||
"history": ResourceUsage.swapUsageHistory,
|
||||
"maxAvailableString": ResourceUsage.maxAvailableSwapString
|
||||
},
|
||||
]
|
||||
|
||||
contentItem: Rectangle {
|
||||
id: contentItem
|
||||
anchors.centerIn: parent
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: root.contentRadius
|
||||
property real padding: 4
|
||||
implicitWidth: 350
|
||||
implicitHeight: 200
|
||||
// implicitHeight: contentColumn.implicitHeight + padding * 2
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: parent.padding
|
||||
}
|
||||
spacing: 8
|
||||
|
||||
SecondaryTabBar {
|
||||
id: tabBar
|
||||
|
||||
currentIndex: Persistent.states.overlay.resources.tabIndex
|
||||
onCurrentIndexChanged: {
|
||||
Persistent.states.overlay.resources.tabIndex = tabBar.currentIndex;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.resources.length
|
||||
delegate: SecondaryTabButton {
|
||||
required property int index
|
||||
property var modelData: root.resources[index]
|
||||
buttonIcon: modelData.icon
|
||||
buttonText: modelData.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResourceSummary {
|
||||
Layout.margins: 8
|
||||
history: root.resources[tabBar.currentIndex]?.history ?? []
|
||||
maxAvailableString: root.resources[tabBar.currentIndex]?.maxAvailableString ?? "--"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ResourceSummary: RowLayout {
|
||||
id: resourceSummary
|
||||
required property list<real> history
|
||||
required property string maxAvailableString
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
StyledText {
|
||||
text: (resourceSummary.history[resourceSummary.history.length - 1] * 100).toFixed(1) + "%"
|
||||
font {
|
||||
family: Appearance.font.family.numbers
|
||||
variableAxes: Appearance.font.variableAxes.numbers
|
||||
pixelSize: Appearance.font.pixelSize.huge
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
text: Translation.tr("of %1").arg(resourceSummary.maxAvailableString)
|
||||
font {
|
||||
// family: Appearance.font.family.numbers
|
||||
// variableAxes: Appearance.font.variableAxes.numbers
|
||||
pixelSize: Appearance.font.pixelSize.smallie
|
||||
}
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: graphBg
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: graphBg.width
|
||||
height: graphBg.height
|
||||
radius: graphBg.radius
|
||||
}
|
||||
}
|
||||
Graph {
|
||||
anchors.fill: parent
|
||||
values: root.resources[tabBar.currentIndex]?.history ?? []
|
||||
points: ResourceUsage.historyLength
|
||||
alignment: Graph.Alignment.Right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.overlay
|
||||
import qs.modules.sidebarRight.volumeMixer
|
||||
|
||||
@@ -10,15 +13,49 @@ StyledOverlayWidget {
|
||||
contentItem: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: root.contentRadius
|
||||
property real padding: 16
|
||||
implicitHeight: 600
|
||||
implicitWidth: 350
|
||||
|
||||
VolumeDialogContent {
|
||||
anchors.fill: parent
|
||||
anchors.margins: parent.padding
|
||||
isSink: true
|
||||
}
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: parent.padding
|
||||
}
|
||||
spacing: 8
|
||||
|
||||
SecondaryTabBar {
|
||||
id: tabBar
|
||||
|
||||
currentIndex: Persistent.states.overlay.volumeMixer.tabIndex
|
||||
onCurrentIndexChanged: {
|
||||
Persistent.states.overlay.volumeMixer.tabIndex = tabBar.currentIndex;
|
||||
}
|
||||
|
||||
SecondaryTabButton {
|
||||
buttonIcon: "media_output"
|
||||
buttonText: Translation.tr("Output")
|
||||
}
|
||||
SecondaryTabButton {
|
||||
buttonIcon: "mic"
|
||||
buttonText: Translation.tr("Input")
|
||||
}
|
||||
}
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: Persistent.states.overlay.volumeMixer.tabIndex
|
||||
onCurrentIndexChanged: {
|
||||
Persistent.states.overlay.volumeMixer.tabIndex = swipeView.currentIndex;
|
||||
}
|
||||
clip: true
|
||||
|
||||
VolumeDialogContent { isSink: true }
|
||||
VolumeDialogContent { isSink: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,6 @@ ContentPage {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,98 @@ import qs.modules.common.widgets
|
||||
ContentPage {
|
||||
forceWidth: true
|
||||
|
||||
ContentSection {
|
||||
icon: "keyboard"
|
||||
title: Translation.tr("Cheat sheet")
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Super key symbol")
|
||||
tooltip: Translation.tr("You can also manually edit cheatsheet.superKey")
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.cheatsheet.superKey
|
||||
onSelected: newValue => {
|
||||
Config.options.cheatsheet.superKey = newValue;
|
||||
}
|
||||
// Use a nerdfont to see the icons
|
||||
options: ([
|
||||
"", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "⌘", "", "", ""
|
||||
]).map(icon => { return {
|
||||
displayName: icon,
|
||||
value: icon
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSwitch {
|
||||
buttonIcon: ""
|
||||
text: Translation.tr("Use macOS-like symbols for mods keys")
|
||||
checked: Config.options.cheatsheet.useMacSymbol
|
||||
onCheckedChanged: {
|
||||
Config.options.cheatsheet.useMacSymbol = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("e.g. for Ctrl, for Alt, for Shift, etc")
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSwitch {
|
||||
buttonIcon: ""
|
||||
text: Translation.tr("Use symbols for function keys")
|
||||
checked: Config.options.cheatsheet.useFnSymbol
|
||||
onCheckedChanged: {
|
||||
Config.options.cheatsheet.useFnSymbol = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("e.g. for F1, for F12")
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
buttonIcon: ""
|
||||
text: Translation.tr("Use symbols for mouse")
|
||||
checked: Config.options.cheatsheet.useMouseSymbol
|
||||
onCheckedChanged: {
|
||||
Config.options.cheatsheet.useMouseSymbol = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Replace for \"Scroll ↓\", \"Scroll ↑\", L \"LMB\", R \"RMB\", \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"")
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
buttonIcon: "highlight_keyboard_focus"
|
||||
text: Translation.tr("Split buttons")
|
||||
checked: Config.options.cheatsheet.splitButtons
|
||||
onCheckedChanged: {
|
||||
Config.options.cheatsheet.splitButtons = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \" + A\" instead of \" A\")")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Keybind font size")
|
||||
value: Config.options.cheatsheet.fontSize.key
|
||||
from: 8
|
||||
to: 30
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.cheatsheet.fontSize.key = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Description font size")
|
||||
value: Config.options.cheatsheet.fontSize.comment
|
||||
from: 8
|
||||
to: 30
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.cheatsheet.fontSize.comment = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentSection {
|
||||
icon: "call_to_action"
|
||||
title: Translation.tr("Dock")
|
||||
@@ -647,4 +739,5 @@ ContentPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property int currentTab: 0
|
||||
property var tabButtonList: [
|
||||
{"name": Translation.tr("Pomodoro"), "icon": "search_activity"},
|
||||
{"name": Translation.tr("Stopwatch"), "icon": "timer"}
|
||||
@@ -17,20 +16,20 @@ Item {
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1)
|
||||
tabBar.incrementCurrentIndex();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
currentTab = Math.max(currentTab - 1, 0)
|
||||
tabBar.decrementCurrentIndex();
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S
|
||||
if (currentTab === 0) {
|
||||
if (tabBar.currentIndex === 0) {
|
||||
TimerService.togglePomodoro()
|
||||
} else {
|
||||
TimerService.toggleStopwatch()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_R) { // Reset with R
|
||||
if (currentTab === 0) {
|
||||
if (tabBar.currentIndex === 0) {
|
||||
TimerService.resetPomodoro()
|
||||
} else {
|
||||
TimerService.stopwatchReset()
|
||||
@@ -46,82 +45,19 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
TabBar {
|
||||
SecondaryTabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
currentIndex: currentTab
|
||||
onCurrentIndexChanged: currentTab = currentIndex
|
||||
|
||||
background: Item {
|
||||
WheelHandler {
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0)
|
||||
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
|
||||
else if (event.angleDelta.y > 0)
|
||||
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
}
|
||||
currentIndex: swipeView.currentIndex
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: SecondaryTabButton {
|
||||
selected: (index == currentTab)
|
||||
buttonText: modelData.name
|
||||
buttonIcon: modelData.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { // Tab indicator
|
||||
id: tabIndicator
|
||||
Layout.fillWidth: true
|
||||
height: 3
|
||||
property bool enableIndicatorAnimation: false
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentTabChanged() {
|
||||
tabIndicator.enableIndicatorAnimation = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
property int tabCount: root.tabButtonList.length
|
||||
property real fullTabSize: root.width / tabCount;
|
||||
property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
|
||||
|
||||
implicitWidth: targetWidth
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
|
||||
|
||||
color: Appearance.colors.colPrimary
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Behavior on x {
|
||||
enabled: tabIndicator.enableIndicatorAnimation
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
enabled: tabIndicator.enableIndicatorAnimation
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Tabbar bottom border
|
||||
id: tabBarBottomBorder
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
Layout.topMargin: 10
|
||||
@@ -129,11 +65,7 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
clip: true
|
||||
currentIndex: currentTab
|
||||
onCurrentIndexChanged: {
|
||||
tabIndicator.enableIndicatorAnimation = true
|
||||
currentTab = currentIndex
|
||||
}
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
// Tabs
|
||||
PomodoroTimer {}
|
||||
|
||||
@@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property int currentTab: 0
|
||||
property var tabButtonList: [{"icon": "checklist", "name": Translation.tr("Unfinished")}, {"name": Translation.tr("Done"), "icon": "check_circle"}]
|
||||
property bool showAddDialog: false
|
||||
property int dialogMargins: 20
|
||||
@@ -17,9 +16,9 @@ Item {
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1)
|
||||
tabBar.incrementCurrentIndex();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
currentTab = Math.max(currentTab - 1, 0)
|
||||
tabBar.decrementCurrentIndex();
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
@@ -39,82 +38,19 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
TabBar {
|
||||
SecondaryTabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
currentIndex: currentTab
|
||||
onCurrentIndexChanged: currentTab = currentIndex
|
||||
|
||||
background: Item {
|
||||
WheelHandler {
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0)
|
||||
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
|
||||
else if (event.angleDelta.y > 0)
|
||||
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
}
|
||||
currentIndex: swipeView.currentIndex
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: SecondaryTabButton {
|
||||
selected: (index == currentTab)
|
||||
buttonText: modelData.name
|
||||
buttonIcon: modelData.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { // Tab indicator
|
||||
id: tabIndicator
|
||||
Layout.fillWidth: true
|
||||
height: 3
|
||||
property bool enableIndicatorAnimation: false
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentTabChanged() {
|
||||
tabIndicator.enableIndicatorAnimation = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
property int tabCount: root.tabButtonList.length
|
||||
property real fullTabSize: root.width / tabCount;
|
||||
property real targetWidth: tabBar?.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0
|
||||
|
||||
implicitWidth: targetWidth
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
|
||||
|
||||
color: Appearance.colors.colPrimary
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Behavior on x {
|
||||
enabled: tabIndicator.enableIndicatorAnimation
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
enabled: tabIndicator.enableIndicatorAnimation
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Tabbar bottom border
|
||||
id: tabBarBottomBorder
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
Layout.topMargin: 10
|
||||
@@ -122,11 +58,7 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
clip: true
|
||||
currentIndex: currentTab
|
||||
onCurrentIndexChanged: {
|
||||
tabIndicator.enableIndicatorAnimation = true
|
||||
currentTab = currentIndex
|
||||
}
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
// To Do tab
|
||||
TaskList {
|
||||
@@ -215,7 +147,7 @@ Item {
|
||||
Todo.addTask(todoInput.text)
|
||||
todoInput.text = ""
|
||||
root.showAddDialog = false
|
||||
root.currentTab = 0 // Show unfinished tasks
|
||||
tabBar.setCurrentIndex(0) // Show unfinished tasks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ ColumnLayout {
|
||||
Layout.topMargin: -22
|
||||
Layout.leftMargin: 0
|
||||
Layout.rightMargin: 0
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
DialogSectionListView {
|
||||
@@ -56,6 +57,7 @@ ColumnLayout {
|
||||
Layout.topMargin: -22
|
||||
Layout.leftMargin: 0
|
||||
Layout.rightMargin: 0
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
DialogSectionListView {
|
||||
|
||||
@@ -28,10 +28,10 @@ Item {
|
||||
sourceSize.height: size
|
||||
source: {
|
||||
let icon;
|
||||
icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]);
|
||||
icon = AppSearch.guessIcon(root.node?.properties["application.icon-name"] ?? "");
|
||||
if (AppSearch.iconExists(icon))
|
||||
return Quickshell.iconPath(icon, "image-missing");
|
||||
icon = AppSearch.guessIcon(root.node.properties["node.name"]);
|
||||
icon = AppSearch.guessIcon(root.node?.properties["node.name"] ?? "");
|
||||
return Quickshell.iconPath(icon, "image-missing");
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ Item {
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
// application.name -> description -> name
|
||||
const app = root.node.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name);
|
||||
const app = root.node?.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name);
|
||||
const media = root.node.properties["media.name"];
|
||||
return media != undefined ? `${app} • ${media}` : app;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ Item {
|
||||
|
||||
StyledSlider {
|
||||
id: slider
|
||||
value: root.node.audio.volume
|
||||
value: root.node?.audio.volume ?? 0
|
||||
onMoved: root.node.audio.volume = value
|
||||
configuration: StyledSlider.Configuration.S
|
||||
}
|
||||
|
||||
@@ -10,17 +10,55 @@ import Quickshell.Io
|
||||
* Simple polled resource usage service with RAM, Swap, and CPU usage.
|
||||
*/
|
||||
Singleton {
|
||||
property double memoryTotal: 1
|
||||
property double memoryFree: 1
|
||||
property double memoryUsed: memoryTotal - memoryFree
|
||||
property double memoryUsedPercentage: memoryUsed / memoryTotal
|
||||
property double swapTotal: 1
|
||||
property double swapFree: 1
|
||||
property double swapUsed: swapTotal - swapFree
|
||||
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
||||
property double cpuUsage: 0
|
||||
id: root
|
||||
property real memoryTotal: 1
|
||||
property real memoryFree: 0
|
||||
property real memoryUsed: memoryTotal - memoryFree
|
||||
property real memoryUsedPercentage: memoryUsed / memoryTotal
|
||||
property real swapTotal: 1
|
||||
property real swapFree: 0
|
||||
property real swapUsed: swapTotal - swapFree
|
||||
property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
||||
property real cpuUsage: 0
|
||||
property var previousCpuStats
|
||||
|
||||
property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal)
|
||||
property string maxAvailableSwapString: kbToGbString(ResourceUsage.swapTotal)
|
||||
property string maxAvailableCpuString: "--"
|
||||
|
||||
readonly property int historyLength: Config?.options.resources.historyLength ?? 60
|
||||
property list<real> cpuUsageHistory: []
|
||||
property list<real> memoryUsageHistory: []
|
||||
property list<real> swapUsageHistory: []
|
||||
|
||||
function kbToGbString(kb) {
|
||||
return (kb / (1024 * 1024)).toFixed(1) + " GB";
|
||||
}
|
||||
|
||||
function updateMemoryUsageHistory() {
|
||||
memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage]
|
||||
if (memoryUsageHistory.length > historyLength) {
|
||||
memoryUsageHistory.shift()
|
||||
}
|
||||
}
|
||||
function updateSwapUsageHistory() {
|
||||
swapUsageHistory = [...swapUsageHistory, swapUsedPercentage]
|
||||
if (swapUsageHistory.length > historyLength) {
|
||||
swapUsageHistory.shift()
|
||||
}
|
||||
}
|
||||
function updateCpuUsageHistory() {
|
||||
cpuUsageHistory = [...cpuUsageHistory, cpuUsage]
|
||||
if (cpuUsageHistory.length > historyLength) {
|
||||
cpuUsageHistory.shift()
|
||||
}
|
||||
}
|
||||
function updateHistories() {
|
||||
updateMemoryUsageHistory()
|
||||
updateSwapUsageHistory()
|
||||
updateCpuUsageHistory()
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1
|
||||
running: true
|
||||
@@ -53,10 +91,24 @@ Singleton {
|
||||
|
||||
previousCpuStats = { total, idle }
|
||||
}
|
||||
|
||||
root.updateHistories()
|
||||
interval = Config.options?.resources?.updateInterval ?? 3000
|
||||
}
|
||||
}
|
||||
|
||||
FileView { id: fileMeminfo; path: "/proc/meminfo" }
|
||||
FileView { id: fileStat; path: "/proc/stat" }
|
||||
|
||||
Process {
|
||||
id: findCpuMaxFreqProc
|
||||
command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"]
|
||||
running: true
|
||||
stdout: StdioCollector {
|
||||
id: outputCollector
|
||||
onStreamFinished: {
|
||||
root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + " GHz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user