forked from Shinonome/dots-hyprland
qs: move panels into modules/ii
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
property Component regionComponent: Component {
|
||||
Region {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: overlayLoader
|
||||
active: GlobalStates.overlayOpen || OverlayContext.hasPinnedWidgets
|
||||
sourceComponent: PanelWindow {
|
||||
id: overlayWindow
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "quickshell:overlay"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
visible: true
|
||||
color: "transparent"
|
||||
|
||||
mask: Region {
|
||||
item: GlobalStates.overlayOpen ? overlayContent : null
|
||||
regions: OverlayContext.clickableWidgets.map((widget) => regionComponent.createObject(this, {
|
||||
item: widget
|
||||
}));
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "overlay"
|
||||
|
||||
function toggle(): void {
|
||||
GlobalStates.overlayOpen = !GlobalStates.overlayOpen;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "overlayToggle"
|
||||
description: "Toggles overlay on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.overlayOpen = !GlobalStates.overlayOpen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.widgets.widgetCanvas
|
||||
|
||||
Item {
|
||||
id: root
|
||||
focus: true
|
||||
readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true
|
||||
|
||||
Keys.onPressed: (event) => { // Esc to close
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
GlobalStates.overlayOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
property real initScale: Config.options.overlay.openingZoomAnimation ? 1.08 : 1.000001
|
||||
scale: initScale
|
||||
Component.onCompleted: {
|
||||
scale = 1
|
||||
}
|
||||
Behavior on scale {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
color: Appearance.colors.colScrim
|
||||
visible: Config.options.overlay.darkenScreen && opacity > 0
|
||||
opacity: (GlobalStates.overlayOpen && root.scale !== initScale) ? 1 : 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
WidgetCanvas {
|
||||
anchors.fill: parent
|
||||
onClicked: GlobalStates.overlayOpen = false
|
||||
|
||||
OverlayTaskbar {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: 50
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Persistent.states.overlay.open.map(identifier => {
|
||||
return OverlayContext.availableWidgets.find(w => w.identifier === identifier);
|
||||
})
|
||||
objectProp: "identifier"
|
||||
}
|
||||
delegate: OverlayWidgetDelegateChooser {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<var> availableWidgets: [
|
||||
{ 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
|
||||
|
||||
property list<string> pinnedWidgetIdentifiers: []
|
||||
property list<var> clickableWidgets: []
|
||||
|
||||
function pin(identifier: string, pin = true) {
|
||||
if (pin) {
|
||||
if (!root.pinnedWidgetIdentifiers.includes(identifier)) {
|
||||
root.pinnedWidgetIdentifiers.push(identifier)
|
||||
}
|
||||
} else {
|
||||
root.pinnedWidgetIdentifiers = root.pinnedWidgetIdentifiers.filter(id => id !== identifier)
|
||||
}
|
||||
}
|
||||
|
||||
function registerClickableWidget(widget: var, clickable = true) {
|
||||
if (clickable) {
|
||||
if (!root.clickableWidgets.includes(widget)) {
|
||||
root.clickableWidgets.push(widget)
|
||||
}
|
||||
} else {
|
||||
root.clickableWidgets = root.clickableWidgets.filter(w => w !== widget)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.widgets.widgetCanvas
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property real padding: 8
|
||||
|
||||
opacity: GlobalStates.overlayOpen ? 1 : 0
|
||||
implicitWidth: contentRow.implicitWidth + (padding * 2)
|
||||
implicitHeight: contentRow.implicitHeight + (padding * 2)
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: Appearance.rounding.large
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: OverlayContext.availableWidgets
|
||||
}
|
||||
delegate: WidgetButton {
|
||||
required property var modelData
|
||||
identifier: modelData.identifier
|
||||
materialSymbol: modelData.materialSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator {}
|
||||
|
||||
TimeWidget {}
|
||||
}
|
||||
|
||||
component Separator: Rectangle {
|
||||
implicitWidth: 1
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
}
|
||||
|
||||
component TimeWidget: StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 6
|
||||
|
||||
text: DateTime.time
|
||||
font {
|
||||
family: Appearance.font.family.numbers
|
||||
variableAxes: Appearance.font.variableAxes.numbers
|
||||
pixelSize: 22
|
||||
}
|
||||
}
|
||||
|
||||
component WidgetButton: RippleButton {
|
||||
id: widgetButton
|
||||
required property string identifier
|
||||
required property string materialSymbol
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
toggled: Persistent.states.overlay.open.includes(identifier)
|
||||
onClicked: {
|
||||
if (widgetButton.toggled) {
|
||||
Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== identifier);
|
||||
} else {
|
||||
Persistent.states.overlay.open.push(identifier);
|
||||
}
|
||||
}
|
||||
implicitWidth: implicitHeight
|
||||
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
|
||||
buttonRadius: root.radius - (root.height - height) / 2
|
||||
|
||||
contentItem: Item {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: 32
|
||||
implicitHeight: 32
|
||||
MaterialSymbol {
|
||||
id: iconWidget
|
||||
anchors.centerIn: parent
|
||||
iconSize: 24
|
||||
text: widgetButton.materialSymbol
|
||||
color: widgetButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.modules.ii.overlay.crosshair
|
||||
import qs.modules.ii.overlay.volumeMixer
|
||||
import qs.modules.ii.overlay.fpsLimiter
|
||||
import qs.modules.ii.overlay.recorder
|
||||
import qs.modules.ii.overlay.resources
|
||||
|
||||
DelegateChooser {
|
||||
id: root
|
||||
role: "identifier"
|
||||
|
||||
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
|
||||
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
|
||||
DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} }
|
||||
DelegateChoice { roleValue: "recorder"; Recorder {} }
|
||||
DelegateChoice { roleValue: "resources"; Resources {} }
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.widgets.widgetCanvas
|
||||
|
||||
/*
|
||||
* To make an overlay widget:
|
||||
* 1. Create a modules/overlay/<yourWidget>/<YourWidget>.qml, using this as the base class and declare your widget content as contentItem
|
||||
* 2. Add an entry to OverlayContext.availableWidgets with identifier=<yourWidgetIdentifier>
|
||||
* 3. Add an entry in Persistent.states.overlay.<yourWidgetIdentifier> with x, y, width, height, pinned, clickthrough properties set to reasonable defaults
|
||||
* 4. Add an entry in OverlayWidgetDelegateChooser with roleValue=<yourWidgetIdentifier> and Declare your widget in there
|
||||
* Use existing entries as reference.
|
||||
*/
|
||||
AbstractOverlayWidget {
|
||||
id: root
|
||||
|
||||
// To be defined by subclasses
|
||||
required property Item contentItem
|
||||
property bool fancyBorders: true
|
||||
property bool showCenterButton: false
|
||||
property bool showClickabilityButton: true
|
||||
|
||||
// Defaults n stuff
|
||||
required property var modelData
|
||||
readonly property string identifier: modelData.identifier
|
||||
readonly property string materialSymbol: modelData.materialSymbol ?? "widgets"
|
||||
property string title: identifier.replace(/([A-Z])/g, " $1").replace(/^./, function(str){ return str.toUpperCase(); })
|
||||
property var persistentStateEntry: Persistent.states.overlay[identifier]
|
||||
property real radius: Appearance.rounding.windowRounding
|
||||
property real minimumWidth: 250
|
||||
property real minimumHeight: 100
|
||||
property real resizeMargin: 8
|
||||
property real padding: 6
|
||||
property real contentRadius: radius - padding
|
||||
|
||||
// Resizing
|
||||
function getXResizeDirection(x) {
|
||||
return (x < root.resizeMargin) ? -1 : (x > root.width - root.resizeMargin) ? 1 : 0
|
||||
}
|
||||
function getYResizeDirection(y) {
|
||||
return (y < root.resizeMargin) ? -1 : (y > root.height - root.resizeMargin) ? 1 : 0
|
||||
}
|
||||
hoverEnabled: true
|
||||
property bool resizable: true
|
||||
property bool resizing: false
|
||||
property int resizeXDirection: getXResizeDirection(mouseX)
|
||||
property int resizeYDirection: getYResizeDirection(mouseY)
|
||||
draggable: GlobalStates.overlayOpen
|
||||
drag.target: undefined
|
||||
animateXPos: !dragHandler.active
|
||||
animateYPos: !dragHandler.active
|
||||
z: dragHandler.active ? 2 : 1
|
||||
cursorShape: {
|
||||
if (dragHandler.active) return root.resizing ? cursorShape : Qt.ArrowCursor;
|
||||
if (resizeMargin < mouseX && mouseX < width - resizeMargin &&
|
||||
resizeMargin < mouseY && mouseY < height - resizeMargin) {
|
||||
return Qt.ArrowCursor;
|
||||
} else {
|
||||
if (!root.resizable) return Qt.ArrowCursor;
|
||||
const dragIsLeft = mouseX < width / 2
|
||||
const dragIsTop = mouseY < height / 2
|
||||
if ((dragIsLeft && dragIsTop) || (!dragIsLeft && !dragIsTop)) {
|
||||
return Qt.SizeFDiagCursor
|
||||
} else {
|
||||
return Qt.SizeBDiagCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Positioning & sizing
|
||||
x: Math.round(persistentStateEntry.x) // Round or it'll be blurry
|
||||
y: Math.round(persistentStateEntry.y) // Round or it'll be blurry
|
||||
pinned: persistentStateEntry.pinned
|
||||
clickthrough: persistentStateEntry.clickthrough
|
||||
drag {
|
||||
minimumX: 0
|
||||
minimumY: 0
|
||||
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
|
||||
property bool actuallyPinned: pinned && open
|
||||
property bool actuallyClickable: !clickthrough && actuallyPinned && open
|
||||
onActuallyPinnedChanged: reportPinnedState();
|
||||
onActuallyClickableChanged: reportClickableState();
|
||||
function reportPinnedState() {
|
||||
OverlayContext.pin(identifier, actuallyPinned);
|
||||
}
|
||||
function reportClickableState() {
|
||||
OverlayContext.registerClickableWidget(contentItem, actuallyClickable);
|
||||
}
|
||||
|
||||
// Self-registeration with OverlayContext
|
||||
Component.onCompleted: {
|
||||
reportPinnedState();
|
||||
reportClickableState();
|
||||
}
|
||||
|
||||
// Hooks
|
||||
onPressed: (event) => {
|
||||
// We're only interested in handling resize here
|
||||
// Early returns
|
||||
if (!root.resizable) return;
|
||||
if (root.resizeMargin < event.x && event.x < root.width - root.resizeMargin &&
|
||||
root.resizeMargin < event.y && event.y < root.height - root.resizeMargin) {
|
||||
return;
|
||||
}
|
||||
// Resizing setup
|
||||
root.resizing = true;
|
||||
root.resizeXDirection = getXResizeDirection(event.x);
|
||||
root.resizeYDirection = getYResizeDirection(event.y);
|
||||
if (root.resizeYDirection !== 0 && root.resizeXDirection === 0) {
|
||||
root.resizeXDirection = event.x < root.width / 2 ? -1 : 1;
|
||||
} else if (root.resizeXDirection !== 0 && root.resizeYDirection === 0) {
|
||||
root.resizeYDirection = event.y < root.height / 2 ? -1 : 1;
|
||||
}
|
||||
}
|
||||
onPositionChanged: (event) => {
|
||||
if (!resizing) return;
|
||||
contentContainer.implicitWidth = Math.max(root.persistentStateEntry.width + dragHandler.xAxis.activeValue * root.resizeXDirection, root.minimumWidth);
|
||||
contentContainer.implicitHeight = Math.max(root.persistentStateEntry.height + dragHandler.yAxis.activeValue * root.resizeYDirection, root.minimumHeight);
|
||||
const negativeXDrag = root.resizeXDirection === -1;
|
||||
const negativeYDrag = root.resizeYDirection === -1;
|
||||
const wantedX = root.persistentStateEntry.x + (negativeXDrag ? dragHandler.xAxis.activeValue : 0)
|
||||
const wantedY = root.persistentStateEntry.y + (negativeYDrag ? dragHandler.yAxis.activeValue : 0)
|
||||
const negativeXDragLimit = root.persistentStateEntry.x + root.persistentStateEntry.width - contentContainer.implicitWidth;
|
||||
const negativeYDragLimit = root.persistentStateEntry.y + root.persistentStateEntry.height - contentContainer.implicitHeight;
|
||||
root.x = negativeXDrag ? Math.min(wantedX, negativeXDragLimit) : wantedX;
|
||||
root.y = negativeYDrag ? Math.min(wantedY, negativeYDragLimit) : wantedY;
|
||||
}
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
target: (root.draggable && !root.resizing) ? root : null
|
||||
onActiveChanged: { // Handle drag release
|
||||
if (!active) {
|
||||
root.resizing = false;
|
||||
root.savePosition();
|
||||
}
|
||||
}
|
||||
xAxis.minimum: 0
|
||||
xAxis.maximum: root.parent?.width - root.width
|
||||
yAxis.minimum: 0
|
||||
yAxis.maximum: root.parent?.height - root.height
|
||||
}
|
||||
|
||||
function close() {
|
||||
Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== root.identifier);
|
||||
}
|
||||
|
||||
function togglePinned() {
|
||||
persistentStateEntry.pinned = !persistentStateEntry.pinned;
|
||||
}
|
||||
|
||||
function toggleClickthrough() {
|
||||
persistentStateEntry.clickthrough = !persistentStateEntry.clickthrough;
|
||||
}
|
||||
|
||||
function savePosition(xPos = root.x, yPos = root.y, width = contentContainer.implicitWidth, height = contentContainer.implicitHeight) {
|
||||
persistentStateEntry.x = Math.round(xPos);
|
||||
persistentStateEntry.y = Math.round(yPos);
|
||||
persistentStateEntry.width = Math.round(width);
|
||||
persistentStateEntry.height = Math.round(height);
|
||||
}
|
||||
|
||||
function center() {
|
||||
const targetX = (root.parent.width - contentColumn.width) / 2 - root.resizeMargin
|
||||
const targetY = (root.parent.height - contentContainer.height) / 2 - titleBar.implicitHeight + border.border.width - root.resizeMargin
|
||||
root.x = targetX
|
||||
root.y = targetY
|
||||
root.savePosition(targetX, targetY)
|
||||
}
|
||||
|
||||
visible: GlobalStates.overlayOpen || actuallyPinned
|
||||
implicitWidth: contentColumn.implicitWidth + resizeMargin * 2
|
||||
implicitHeight: contentColumn.implicitHeight + resizeMargin * 2
|
||||
|
||||
Rectangle {
|
||||
id: border
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.resizeMargin
|
||||
}
|
||||
color: ColorUtils.transparentize(Appearance.colors.colLayer1, (root.fancyBorders && GlobalStates.overlayOpen) ? 0 : 1)
|
||||
radius: root.radius
|
||||
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
|
||||
border.width: 1
|
||||
|
||||
layer.enabled: GlobalStates.overlayOpen
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: border.width
|
||||
height: border.height
|
||||
radius: root.radius
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
z: root.fancyBorders ? 0 : -1
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Title bar
|
||||
Rectangle {
|
||||
id: titleBar
|
||||
opacity: GlobalStates.overlayOpen ? 1 : 0
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: titleBarRow.implicitWidth + root.padding * 2
|
||||
implicitHeight: titleBarRow.implicitHeight + root.padding * 2
|
||||
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1
|
||||
// border.color: Appearance.colors.colOutlineVariant
|
||||
// border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: titleBarRow
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
spacing: 2
|
||||
|
||||
MaterialSymbol {
|
||||
text: root.materialSymbol
|
||||
Layout.leftMargin: 6
|
||||
iconSize: 20
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: 4
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.title
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
visible: root.showCenterButton
|
||||
materialSymbol: "recenter"
|
||||
onClicked: root.center()
|
||||
StyledToolTip {
|
||||
text: "Center"
|
||||
}
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
visible: (root.pinned && root.showClickabilityButton)
|
||||
materialSymbol: "mouse"
|
||||
toggled: !root.clickthrough
|
||||
onClicked: root.toggleClickthrough()
|
||||
StyledToolTip {
|
||||
text: "Clickable when pinned"
|
||||
}
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
materialSymbol: "keep"
|
||||
toggled: root.pinned
|
||||
onClicked: root.togglePinned()
|
||||
StyledToolTip {
|
||||
text: "Pin"
|
||||
}
|
||||
}
|
||||
|
||||
TitlebarButton {
|
||||
materialSymbol: "close"
|
||||
onClicked: root.close()
|
||||
StyledToolTip {
|
||||
text: "Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Item {
|
||||
id: contentContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: root.fancyBorders ? root.padding : 0
|
||||
Layout.topMargin: -border.border.width // Border of a rectangle is drawn inside its bounds, so we do this to make the gap not too big
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
implicitWidth: Math.max(root.persistentStateEntry.width, root.minimumWidth)
|
||||
implicitHeight: Math.max(root.persistentStateEntry.height, root.minimumHeight)
|
||||
children: [root.contentItem]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
component TitlebarButton: RippleButton {
|
||||
id: titlebarButton
|
||||
required property string materialSymbol
|
||||
buttonRadius: height / 2
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
implicitWidth: implicitHeight
|
||||
padding: 0
|
||||
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
|
||||
contentItem: Item {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: 30
|
||||
implicitHeight: 30
|
||||
|
||||
MaterialSymbol {
|
||||
id: iconWidget
|
||||
anchors.centerIn: parent
|
||||
iconSize: 20
|
||||
text: titlebarButton.materialSymbol
|
||||
fill: titlebarButton.toggled
|
||||
color: titlebarButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.modules.common
|
||||
import qs.modules.ii.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
fancyBorders: false // Crosshair should be see-through
|
||||
showCenterButton: true
|
||||
opacity: 1 // The crosshair itself already has transparency if configured
|
||||
showClickabilityButton: false
|
||||
clickthrough: true
|
||||
resizable: false
|
||||
|
||||
contentItem: CrosshairContent {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Keys to props
|
||||
// f, 0f, 1f, m are irrelevant as they're firing error stuff
|
||||
// 0 is irrelevant because it's some profile stuff
|
||||
property var propertyMap: ({
|
||||
"c": "color",
|
||||
"u": "colorCode",
|
||||
"h": "outline",
|
||||
"o": "outlineOpacity",
|
||||
"t": "outlineThickness",
|
||||
"d": "centerDot",
|
||||
"a": "centerDotOpacity",
|
||||
"z": "centerDotSize",
|
||||
"0a": "innerLineOpacity",
|
||||
"0l": "innerLineLength",
|
||||
"0v": "innerLineVerticalLength",
|
||||
"0g": "innerLineUnbindAxesLengths",
|
||||
"0t": "innerLineThickness",
|
||||
"0o": "innerLineOffset",
|
||||
"1b": "outerLines",
|
||||
"1a": "outerLineOpacity",
|
||||
"1l": "outerLineLength",
|
||||
"1v": "outerLineVerticalLength",
|
||||
"1g": "outerLineUnbindAxesLengths",
|
||||
"1t": "outerLineThickness",
|
||||
"1o": "outerLineOffset",
|
||||
})
|
||||
property var colorMap: ({
|
||||
0: "#FFFFFF",
|
||||
1: "#00FF00",
|
||||
2: "#7FFF00",
|
||||
3: "#DFFF00",
|
||||
4: "#FFFF00",
|
||||
5: "#00FFFF",
|
||||
6: "#FF00FF",
|
||||
7: "#FF0000"
|
||||
})
|
||||
|
||||
// Raw props
|
||||
property int color: 0
|
||||
property string colorCode: "#FFFFFF"
|
||||
property bool outline: true
|
||||
property real outlineOpacity: 0.5
|
||||
property int outlineThickness: 1
|
||||
property bool centerDot: false
|
||||
property real centerDotOpacity: 1
|
||||
property int centerDotSize: 2
|
||||
property bool innerLines: true
|
||||
property real innerLineOpacity: 0.8
|
||||
property int innerLineLength: 6
|
||||
property int innerLineVerticalLength: innerLineLength
|
||||
property bool innerLineUnbindAxesLengths: false
|
||||
property int innerLineThickness: 2
|
||||
property int innerLineOffset: 3
|
||||
property bool outerLines: true
|
||||
property real outerLineOpacity: 0.35
|
||||
property int outerLineLength: 2
|
||||
property int outerLineVerticalLength: outerLineLength
|
||||
property bool outerLineUnbindAxesLengths: false
|
||||
property int outerLineThickness: 2
|
||||
property int outerLineOffset: 10
|
||||
property string defaultCode: "c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10"
|
||||
|
||||
function loadFromCode(code: string): void {
|
||||
let args = code.split(";");
|
||||
for (let i = 0; i < args.length; i+= 2) {
|
||||
let key = args[i];
|
||||
let value = args[i+1];
|
||||
let targetKey = root.propertyMap[key];
|
||||
let targetType = typeof root[targetKey];
|
||||
|
||||
if (targetKey === undefined) continue;
|
||||
|
||||
if (targetType === "number") {
|
||||
value = parseFloat(value);
|
||||
} else if (targetType === "boolean") {
|
||||
value = (value === "1");
|
||||
}
|
||||
if (targetKey === "colorCode") {
|
||||
value = "#" + value.slice(0, 6);
|
||||
}
|
||||
root[targetKey] = value;
|
||||
}
|
||||
|
||||
if (!root.innerLineUnbindAxesLengths) {
|
||||
root.innerLineVerticalLength = root.innerLineLength;
|
||||
}
|
||||
if (!root.outerLineUnbindAxesLengths) {
|
||||
root.outerLineVerticalLength = root.outerLineLength;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update values from code
|
||||
property var code: Config.options.crosshair.code
|
||||
Component.onCompleted: reloadFromCode();
|
||||
onCodeChanged: reloadFromCode();
|
||||
function reloadFromCode() {
|
||||
root.loadFromCode(root.defaultCode);
|
||||
root.loadFromCode(root.code);
|
||||
}
|
||||
|
||||
// Aggregated props
|
||||
property color crosshairColor: {
|
||||
if (colorMap[color] !== undefined) return root.colorMap[color];
|
||||
if (color === 8) return colorCode;
|
||||
return "#FFFFFF";
|
||||
}
|
||||
property int borderWidth: outline ? outlineThickness : 0
|
||||
property color borderColor: ColorUtils.transparentize("black", 1 - root.outlineOpacity)
|
||||
property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity)
|
||||
property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity)
|
||||
property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset
|
||||
property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset
|
||||
property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2
|
||||
property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2
|
||||
property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2
|
||||
implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction
|
||||
implicitHeight: implicitWidth
|
||||
// width: implicitWidth
|
||||
// height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: centerDot
|
||||
visible: root.centerDot
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: root.crosshairColor
|
||||
opacity: root.centerDotOpacity
|
||||
width: centerDotTotalSize
|
||||
height: width
|
||||
|
||||
border.width: root.borderWidth
|
||||
border.color: root.borderColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: innerLines
|
||||
model: 4
|
||||
Item {
|
||||
id: innerHair
|
||||
z: index % 2 // Vertical lines above horizontal lines
|
||||
required property int index
|
||||
property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0
|
||||
property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength)
|
||||
visible: root.innerLines && hairLength > 0
|
||||
anchors.fill: parent
|
||||
rotation: index * 90
|
||||
Rectangle {
|
||||
x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection
|
||||
y: parent.height / 2 - height / 2
|
||||
|
||||
color: root.innerLineColor
|
||||
width: innerHair.hairLength + root.borderWidth * 2
|
||||
height: root.innerLineThickness + root.borderWidth * 2
|
||||
|
||||
border.width: root.borderWidth
|
||||
border.color: root.borderColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: outerLines
|
||||
model: 4
|
||||
Item {
|
||||
id: outerHair
|
||||
z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines
|
||||
required property int index
|
||||
property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0
|
||||
property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength)
|
||||
visible: root.outerLines && hairLength > 0
|
||||
anchors.fill: parent
|
||||
rotation: index * 90
|
||||
Rectangle {
|
||||
x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection
|
||||
y: parent.height / 2 - height / 2
|
||||
|
||||
color: root.outerLineColor
|
||||
width: hairLength + root.borderWidth * 2
|
||||
height: root.outerLineThickness + root.borderWidth * 2
|
||||
|
||||
border.width: root.borderWidth
|
||||
border.color: root.borderColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.modules.common
|
||||
import qs.modules.ii.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
title: "MangoHud FPS"
|
||||
minimumWidth: 275
|
||||
minimumHeight: 100
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.ii.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
minimumWidth: 310
|
||||
minimumHeight: 130
|
||||
|
||||
contentItem: Rectangle {
|
||||
id: contentItem
|
||||
anchors.fill: parent
|
||||
radius: root.contentRadius
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
property real padding: 8
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
Row {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
spacing: 10
|
||||
|
||||
BigRecorderButton {
|
||||
materialSymbol: "screenshot_region"
|
||||
name: "Screenshot region"
|
||||
onClicked: {
|
||||
GlobalStates.overlayOpen = false;
|
||||
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]);
|
||||
}
|
||||
}
|
||||
|
||||
BigRecorderButton {
|
||||
materialSymbol: "photo_camera"
|
||||
name: "Screenshot"
|
||||
onClicked: {
|
||||
GlobalStates.overlayOpen = false;
|
||||
Quickshell.execDetached(["bash", "-c", "grim - | wl-copy"]);
|
||||
}
|
||||
}
|
||||
|
||||
BigRecorderButton {
|
||||
materialSymbol: "screen_record"
|
||||
name: "Record region"
|
||||
onClicked: {
|
||||
GlobalStates.overlayOpen = false;
|
||||
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "recordWithSound"]);
|
||||
}
|
||||
}
|
||||
|
||||
BigRecorderButton {
|
||||
materialSymbol: "capture"
|
||||
name: "Record screen"
|
||||
onClicked: {
|
||||
GlobalStates.overlayOpen = false;
|
||||
Quickshell.execDetached([Directories.recordScriptPath, "--fullscreen", "--sound"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RippleButton {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
buttonRadius: height / 2
|
||||
colBackground: Appearance.colors.colLayer3
|
||||
colBackgroundHover: Appearance.colors.colLayer3Hover
|
||||
colRipple: Appearance.colors.colLayer3Active
|
||||
onClicked: {
|
||||
GlobalStates.overlayOpen = false;
|
||||
Qt.openUrlExternally(`file://${Config.options.screenRecord.savePath}`);
|
||||
}
|
||||
contentItem: Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
MaterialSymbol {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "animated_images"
|
||||
iconSize: 20
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Open recordings folder")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component BigRecorderButton: RippleButton {
|
||||
id: bigButton
|
||||
required property string materialSymbol
|
||||
required property string name
|
||||
implicitHeight: 66
|
||||
implicitWidth: 66
|
||||
buttonRadius: height / 2
|
||||
|
||||
colBackground: Appearance.colors.colLayer3
|
||||
colBackgroundHover: Appearance.colors.colLayer3Hover
|
||||
colRipple: Appearance.colors.colLayer3Active
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: bigButton.materialSymbol
|
||||
iconSize: 28
|
||||
}
|
||||
|
||||
StyledToolTip {
|
||||
text: bigButton.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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.ii.overlay
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
minimumWidth: 300
|
||||
minimumHeight: 200
|
||||
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.fill: parent
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: root.contentRadius
|
||||
property real padding: 4
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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.ii.overlay
|
||||
import qs.modules.ii.sidebarRight.volumeMixer
|
||||
|
||||
StyledOverlayWidget {
|
||||
id: root
|
||||
minimumWidth: 300
|
||||
minimumHeight: 380
|
||||
|
||||
contentItem: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: root.contentRadius
|
||||
property real padding: 6
|
||||
|
||||
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
|
||||
|
||||
PaddedVolumeDialogContent {
|
||||
isSink: true
|
||||
}
|
||||
PaddedVolumeDialogContent {
|
||||
isSink: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component PaddedVolumeDialogContent: Item {
|
||||
id: paddedVolumeDialogContent
|
||||
property alias isSink: volDialogContent.isSink
|
||||
property real padding: 12
|
||||
implicitWidth: volDialogContent.implicitWidth + padding * 2
|
||||
implicitHeight: volDialogContent.implicitHeight + padding * 2
|
||||
|
||||
VolumeDialogContent {
|
||||
id: volDialogContent
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: paddedVolumeDialogContent.padding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user