mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 10:32:07 -04:00
Compare commits
9 Commits
e7c8d208e2
...
f0be36062e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0be36062e | ||
|
|
9578d6daf9 | ||
|
|
cc6766135d | ||
|
|
28c9bb0925 | ||
|
|
7826d827dd | ||
|
|
7f392acc54 | ||
|
|
190fd662ad | ||
|
|
e18587c471 | ||
|
|
ddb079b62d |
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int settingsConfigVersion: 6
|
readonly property int settingsConfigVersion: 5
|
||||||
|
|
||||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||||
|
|
||||||
@@ -150,6 +150,7 @@ Singleton {
|
|||||||
property int mangoLayoutBorderSize: -1
|
property int mangoLayoutBorderSize: -1
|
||||||
|
|
||||||
property int firstDayOfWeek: -1
|
property int firstDayOfWeek: -1
|
||||||
|
property bool showWeekNumber: false
|
||||||
property bool use24HourClock: true
|
property bool use24HourClock: true
|
||||||
property bool showSeconds: false
|
property bool showSeconds: false
|
||||||
property bool padHours12Hour: false
|
property bool padHours12Hour: false
|
||||||
@@ -453,6 +454,11 @@ Singleton {
|
|||||||
property bool syncModeWithPortal: true
|
property bool syncModeWithPortal: true
|
||||||
property bool terminalsAlwaysDark: false
|
property bool terminalsAlwaysDark: false
|
||||||
|
|
||||||
|
property string muxType: "tmux"
|
||||||
|
property bool muxUseCustomCommand: false
|
||||||
|
property string muxCustomCommand: ""
|
||||||
|
property string muxSessionFilter: ""
|
||||||
|
|
||||||
property bool runDmsMatugenTemplates: true
|
property bool runDmsMatugenTemplates: true
|
||||||
property bool matugenTemplateGtk: true
|
property bool matugenTemplateGtk: true
|
||||||
property bool matugenTemplateNiri: true
|
property bool matugenTemplateNiri: true
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ var SPEC = {
|
|||||||
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
|
|
||||||
firstDayOfWeek: { def: -1 },
|
firstDayOfWeek: { def: -1 },
|
||||||
|
showWeekNumber: { def: false },
|
||||||
use24HourClock: { def: true },
|
use24HourClock: { def: true },
|
||||||
showSeconds: { def: false },
|
showSeconds: { def: false },
|
||||||
padHours12Hour: { def: false },
|
padHours12Hour: { def: false },
|
||||||
@@ -268,6 +269,11 @@ var SPEC = {
|
|||||||
syncModeWithPortal: { def: true },
|
syncModeWithPortal: { def: true },
|
||||||
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
||||||
|
|
||||||
|
muxType: { def: "tmux" },
|
||||||
|
muxUseCustomCommand: { def: false },
|
||||||
|
muxCustomCommand: { def: "" },
|
||||||
|
muxSessionFilter: { def: "" },
|
||||||
|
|
||||||
runDmsMatugenTemplates: { def: true },
|
runDmsMatugenTemplates: { def: true },
|
||||||
matugenTemplateGtk: { def: true },
|
matugenTemplateGtk: { def: true },
|
||||||
matugenTemplateNiri: { def: true },
|
matugenTemplateNiri: { def: true },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import qs.Modals.Clipboard
|
|||||||
import qs.Modals.Greeter
|
import qs.Modals.Greeter
|
||||||
import qs.Modals.Settings
|
import qs.Modals.Settings
|
||||||
import qs.Modals.DankLauncherV2
|
import qs.Modals.DankLauncherV2
|
||||||
|
import qs.Modals
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
import qs.Modules.AppDrawer
|
import qs.Modules.AppDrawer
|
||||||
import qs.Modules.DankDash
|
import qs.Modules.DankDash
|
||||||
@@ -619,6 +620,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MuxModal {
|
||||||
|
id: muxModal
|
||||||
|
}
|
||||||
|
|
||||||
ClipboardHistoryModal {
|
ClipboardHistoryModal {
|
||||||
id: clipboardHistoryModalPopup
|
id: clipboardHistoryModalPopup
|
||||||
|
|
||||||
|
|||||||
312
quickshell/Modals/Common/InputModal.qml
Normal file
312
quickshell/Modals/Common/InputModal.qml
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
layerNamespace: "dms:input-modal"
|
||||||
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
|
property string inputTitle: ""
|
||||||
|
property string inputMessage: ""
|
||||||
|
property string inputPlaceholder: ""
|
||||||
|
property string inputText: ""
|
||||||
|
property string confirmButtonText: "Confirm"
|
||||||
|
property string cancelButtonText: "Cancel"
|
||||||
|
property color confirmButtonColor: Theme.primary
|
||||||
|
property var onConfirm: function (text) {}
|
||||||
|
property var onCancel: function () {}
|
||||||
|
property int selectedButton: -1
|
||||||
|
property bool keyboardNavigation: false
|
||||||
|
|
||||||
|
function show(title, message, onConfirmCallback, onCancelCallback) {
|
||||||
|
inputTitle = title || "";
|
||||||
|
inputMessage = message || "";
|
||||||
|
inputPlaceholder = "";
|
||||||
|
inputText = "";
|
||||||
|
confirmButtonText = "Confirm";
|
||||||
|
cancelButtonText = "Cancel";
|
||||||
|
confirmButtonColor = Theme.primary;
|
||||||
|
onConfirm = onConfirmCallback || ((text) => {});
|
||||||
|
onCancel = onCancelCallback || (() => {});
|
||||||
|
selectedButton = -1;
|
||||||
|
keyboardNavigation = false;
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWithOptions(options) {
|
||||||
|
inputTitle = options.title || "";
|
||||||
|
inputMessage = options.message || "";
|
||||||
|
inputPlaceholder = options.placeholder || "";
|
||||||
|
inputText = options.initialText || "";
|
||||||
|
confirmButtonText = options.confirmText || "Confirm";
|
||||||
|
cancelButtonText = options.cancelText || "Cancel";
|
||||||
|
confirmButtonColor = options.confirmColor || Theme.primary;
|
||||||
|
onConfirm = options.onConfirm || ((text) => {});
|
||||||
|
onCancel = options.onCancel || (() => {});
|
||||||
|
selectedButton = -1;
|
||||||
|
keyboardNavigation = false;
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmAndClose() {
|
||||||
|
const text = inputText;
|
||||||
|
close();
|
||||||
|
if (onConfirm) {
|
||||||
|
onConfirm(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelAndClose() {
|
||||||
|
close();
|
||||||
|
if (onCancel) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectButton() {
|
||||||
|
if (selectedButton === 0) {
|
||||||
|
cancelAndClose();
|
||||||
|
} else {
|
||||||
|
confirmAndClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldBeVisible: false
|
||||||
|
allowStacking: true
|
||||||
|
modalWidth: 350
|
||||||
|
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 200
|
||||||
|
enableShadow: true
|
||||||
|
shouldHaveFocus: true
|
||||||
|
onBackgroundClicked: cancelAndClose()
|
||||||
|
onOpened: {
|
||||||
|
Qt.callLater(function () {
|
||||||
|
if (contentLoader.item && contentLoader.item.textInputRef) {
|
||||||
|
contentLoader.item.textInputRef.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
FocusScope {
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitHeight: mainColumn.implicitHeight
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
property alias textInputRef: textInput
|
||||||
|
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
const textFieldFocused = textInput.activeFocus;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Escape:
|
||||||
|
root.cancelAndClose();
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Tab:
|
||||||
|
if (textFieldFocused) {
|
||||||
|
root.keyboardNavigation = true;
|
||||||
|
root.selectedButton = 0;
|
||||||
|
textInput.focus = false;
|
||||||
|
} else {
|
||||||
|
root.keyboardNavigation = true;
|
||||||
|
if (root.selectedButton === -1) {
|
||||||
|
root.selectedButton = 0;
|
||||||
|
} else if (root.selectedButton === 0) {
|
||||||
|
root.selectedButton = 1;
|
||||||
|
} else {
|
||||||
|
root.selectedButton = -1;
|
||||||
|
textInput.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left:
|
||||||
|
if (!textFieldFocused) {
|
||||||
|
root.keyboardNavigation = true;
|
||||||
|
root.selectedButton = 0;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Right:
|
||||||
|
if (!textFieldFocused) {
|
||||||
|
root.keyboardNavigation = true;
|
||||||
|
root.selectedButton = 1;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Return:
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
if (root.selectedButton !== -1) {
|
||||||
|
root.selectButton();
|
||||||
|
} else {
|
||||||
|
root.confirmAndClose();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.leftMargin: Theme.spacingL
|
||||||
|
anchors.rightMargin: Theme.spacingL
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.inputTitle
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingL
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.inputMessage
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
visible: root.inputMessage !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: root.inputMessage !== "" ? Theme.spacingL : 0
|
||||||
|
visible: root.inputMessage !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceVariantAlpha
|
||||||
|
border.color: textInput.activeFocus ? Theme.primary : "transparent"
|
||||||
|
border.width: textInput.activeFocus ? 1 : 0
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
id: textInput
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
selectionColor: Theme.primary
|
||||||
|
selectedTextColor: Theme.primaryText
|
||||||
|
clip: true
|
||||||
|
text: root.inputText
|
||||||
|
onTextChanged: root.inputText = text
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.fill: parent
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
text: root.inputPlaceholder
|
||||||
|
visible: textInput.text === "" && !textInput.activeFocus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingL * 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (root.keyboardNavigation && root.selectedButton === 0) {
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||||
|
} else if (cancelButton.containsMouse) {
|
||||||
|
return Theme.surfacePressed;
|
||||||
|
} else {
|
||||||
|
return Theme.surfaceVariantAlpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
border.color: (root.keyboardNavigation && root.selectedButton === 0) ? Theme.primary : "transparent"
|
||||||
|
border.width: (root.keyboardNavigation && root.selectedButton === 0) ? 1 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.cancelButtonText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.cancelAndClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
const baseColor = root.confirmButtonColor;
|
||||||
|
if (root.keyboardNavigation && root.selectedButton === 1) {
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
|
||||||
|
} else if (confirmButton.containsMouse) {
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
|
||||||
|
} else {
|
||||||
|
return baseColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
border.color: (root.keyboardNavigation && root.selectedButton === 1) ? "white" : "transparent"
|
||||||
|
border.width: (root.keyboardNavigation && root.selectedButton === 1) ? 1 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.confirmButtonText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primaryText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: confirmButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.confirmAndClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -207,9 +207,12 @@ Rectangle {
|
|||||||
selectedActionIndex = 0;
|
selectedActionIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleAction() {
|
function cycleAction(reverse = false) {
|
||||||
if (actions.length > 0) {
|
if (actions.length > 0) {
|
||||||
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
if (! reverse)
|
||||||
|
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
||||||
|
else
|
||||||
|
selectedActionIndex = (selectedActionIndex - 1) % actions.length;
|
||||||
ensureSelectedVisible();
|
ensureSelectedVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,10 +353,13 @@ Item {
|
|||||||
performSearch();
|
performSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleMode() {
|
function cycleMode(reverse = false) {
|
||||||
var modes = ["all", "apps", "files", "plugins"];
|
var modes = ["all", "apps", "files", "plugins"];
|
||||||
var currentIndex = modes.indexOf(searchMode);
|
var currentIndex = modes.indexOf(searchMode);
|
||||||
var nextIndex = (currentIndex + 1) % modes.length;
|
if (!reverse)
|
||||||
|
var nextIndex = (currentIndex + 1) % modes.length;
|
||||||
|
else
|
||||||
|
var nextIndex = (currentIndex - 1 + modes.length) % modes.length;
|
||||||
setMode(modes[nextIndex]);
|
setMode(modes[nextIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ FocusScope {
|
|||||||
controller.selectPageUp(8);
|
controller.selectPageUp(8);
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Right:
|
case Qt.Key_Right:
|
||||||
|
if (hasCtrl) {
|
||||||
|
controller.cycleMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||||
controller.selectRight();
|
controller.selectRight();
|
||||||
return;
|
return;
|
||||||
@@ -165,12 +169,25 @@ FocusScope {
|
|||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Left:
|
case Qt.Key_Left:
|
||||||
|
if (hasCtrl) {
|
||||||
|
const reverse = true;
|
||||||
|
controller.cycleMode(reverse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||||
controller.selectLeft();
|
controller.selectLeft();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
return;
|
return;
|
||||||
|
case Qt.Key_H:
|
||||||
|
if (hasCtrl) {
|
||||||
|
const reverse = true;
|
||||||
|
controller.cycleMode(reverse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.accepted = false;
|
||||||
|
return;
|
||||||
case Qt.Key_J:
|
case Qt.Key_J:
|
||||||
if (hasCtrl) {
|
if (hasCtrl) {
|
||||||
controller.selectNext();
|
controller.selectNext();
|
||||||
@@ -185,6 +202,13 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
return;
|
return;
|
||||||
|
case Qt.Key_L:
|
||||||
|
if (hasCtrl) {
|
||||||
|
controller.cycleMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.accepted = false;
|
||||||
|
return;
|
||||||
case Qt.Key_N:
|
case Qt.Key_N:
|
||||||
if (hasCtrl) {
|
if (hasCtrl) {
|
||||||
controller.selectNextSection();
|
controller.selectNextSection();
|
||||||
@@ -200,13 +224,19 @@ FocusScope {
|
|||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Tab:
|
case Qt.Key_Tab:
|
||||||
if (actionPanel.hasActions) {
|
if (hasCtrl && actionPanel.hasActions) {
|
||||||
actionPanel.expanded ? actionPanel.cycleAction() : actionPanel.show();
|
actionPanel.expanded ? actionPanel.cycleAction() : actionPanel.show();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
controller.selectNext();
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Backtab:
|
case Qt.Key_Backtab:
|
||||||
if (actionPanel.expanded)
|
if (hasCtrl && actionPanel.expanded) {
|
||||||
actionPanel.hide();
|
const reverse = true
|
||||||
|
actionPanel.expanded ? actionPanel.cycleAction(reverse) : actionPanel.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.selectPrevious();
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
case Qt.Key_Enter:
|
case Qt.Key_Enter:
|
||||||
@@ -388,7 +418,7 @@ FocusScope {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Tab " + I18n.tr("actions")
|
text: "Ctrl-Tab " + I18n.tr("actions")
|
||||||
font.pixelSize: Theme.fontSizeSmall - 1
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
visible: actionPanel.hasActions
|
visible: actionPanel.hasActions
|
||||||
|
|||||||
621
quickshell/Modals/MuxModal.qml
Normal file
621
quickshell/Modals/MuxModal.qml
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: muxModal
|
||||||
|
|
||||||
|
layerNamespace: "dms:mux"
|
||||||
|
|
||||||
|
property int selectedIndex: -1
|
||||||
|
property string searchText: ""
|
||||||
|
property var filteredSessions: []
|
||||||
|
|
||||||
|
function updateFilteredSessions() {
|
||||||
|
var filtered = []
|
||||||
|
var lowerSearch = searchText.trim().toLowerCase()
|
||||||
|
for (var i = 0; i < MuxService.sessions.length; i++) {
|
||||||
|
var session = MuxService.sessions[i]
|
||||||
|
if (lowerSearch.length > 0 && !session.name.toLowerCase().includes(lowerSearch))
|
||||||
|
continue
|
||||||
|
filtered.push(session)
|
||||||
|
}
|
||||||
|
filteredSessions = filtered
|
||||||
|
|
||||||
|
if (selectedIndex >= filteredSessions.length) {
|
||||||
|
selectedIndex = Math.max(0, filteredSessions.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchTextChanged: updateFilteredSessions()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: MuxService
|
||||||
|
function onSessionsChanged() {
|
||||||
|
updateFilteredSessions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
id: grab
|
||||||
|
windows: [muxModal.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (shouldBeVisible) {
|
||||||
|
hide()
|
||||||
|
} else {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
open()
|
||||||
|
selectedIndex = -1
|
||||||
|
searchText = ""
|
||||||
|
MuxService.refreshSessions()
|
||||||
|
shouldHaveFocus = true
|
||||||
|
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (muxPanel && muxPanel.searchField) {
|
||||||
|
muxPanel.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
close()
|
||||||
|
selectedIndex = -1
|
||||||
|
searchText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachToSession(name) {
|
||||||
|
MuxService.attachToSession(name)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameSession(name) {
|
||||||
|
inputModal.showWithOptions({
|
||||||
|
title: I18n.tr("Rename Session"),
|
||||||
|
message: I18n.tr("Enter a new name for session \"%1\"").arg(name),
|
||||||
|
initialText: name,
|
||||||
|
onConfirm: function (newName) {
|
||||||
|
MuxService.renameSession(name, newName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function killSession(name) {
|
||||||
|
confirmModal.showWithOptions({
|
||||||
|
title: I18n.tr("Kill Session"),
|
||||||
|
message: I18n.tr("Are you sure you want to kill session \"%1\"?").arg(name),
|
||||||
|
confirmText: I18n.tr("Kill"),
|
||||||
|
confirmColor: Theme.primary,
|
||||||
|
onConfirm: function () {
|
||||||
|
MuxService.killSession(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewSession() {
|
||||||
|
inputModal.showWithOptions({
|
||||||
|
title: I18n.tr("New Session"),
|
||||||
|
message: I18n.tr("Please write a name for your new %1 session").arg(MuxService.displayName),
|
||||||
|
onConfirm: function (name) {
|
||||||
|
MuxService.createSession(name)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, filteredSessions.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrevious() {
|
||||||
|
selectedIndex = Math.max(selectedIndex - 1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSelected() {
|
||||||
|
if (selectedIndex === -1) {
|
||||||
|
createNewSession()
|
||||||
|
} else if (selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||||
|
attachToSession(filteredSessions[selectedIndex].name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
modalWidth: 600
|
||||||
|
modalHeight: 600
|
||||||
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
cornerRadius: Theme.cornerRadius
|
||||||
|
borderColor: Theme.outlineMedium
|
||||||
|
borderWidth: 1
|
||||||
|
enableShadow: true
|
||||||
|
keepContentLoaded: true
|
||||||
|
|
||||||
|
onBackgroundClicked: hide()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 3000
|
||||||
|
running: muxModal.shouldBeVisible
|
||||||
|
repeat: true
|
||||||
|
onTriggered: MuxService.refreshSessions()
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
function open(): string {
|
||||||
|
muxModal.show()
|
||||||
|
return "MUX_OPEN_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): string {
|
||||||
|
muxModal.hide()
|
||||||
|
return "MUX_CLOSE_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(): string {
|
||||||
|
muxModal.toggle()
|
||||||
|
return "MUX_TOGGLE_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
target: "mux"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility
|
||||||
|
IpcHandler {
|
||||||
|
function open(): string {
|
||||||
|
muxModal.show()
|
||||||
|
return "TMUX_OPEN_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): string {
|
||||||
|
muxModal.hide()
|
||||||
|
return "TMUX_CLOSE_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(): string {
|
||||||
|
muxModal.toggle()
|
||||||
|
return "TMUX_TOGGLE_SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
target: "tmux"
|
||||||
|
}
|
||||||
|
|
||||||
|
InputModal {
|
||||||
|
id: inputModal
|
||||||
|
onShouldBeVisibleChanged: {
|
||||||
|
if (shouldBeVisible) {
|
||||||
|
muxModal.shouldHaveFocus = false;
|
||||||
|
muxModal.contentWindow.visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (muxModal.shouldBeVisible) {
|
||||||
|
muxModal.contentWindow.visible = true;
|
||||||
|
}
|
||||||
|
Qt.callLater(function () {
|
||||||
|
if (!muxModal.shouldBeVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
muxModal.shouldHaveFocus = true;
|
||||||
|
muxModal.modalFocusScope.forceActiveFocus();
|
||||||
|
if (muxPanel.searchField) {
|
||||||
|
muxPanel.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmModal {
|
||||||
|
id: confirmModal
|
||||||
|
onShouldBeVisibleChanged: {
|
||||||
|
if (shouldBeVisible) {
|
||||||
|
muxModal.shouldHaveFocus = false;
|
||||||
|
muxModal.contentWindow.visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (muxModal.shouldBeVisible) {
|
||||||
|
muxModal.contentWindow.visible = true;
|
||||||
|
}
|
||||||
|
Qt.callLater(function () {
|
||||||
|
if (!muxModal.shouldBeVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
muxModal.shouldHaveFocus = true;
|
||||||
|
muxModal.modalFocusScope.forceActiveFocus();
|
||||||
|
if (muxPanel.searchField) {
|
||||||
|
muxPanel.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
directContent: Item {
|
||||||
|
id: muxPanel
|
||||||
|
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
property alias searchField: searchField
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if ((event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier)) ||
|
||||||
|
(event.key === Qt.Key_Down)) {
|
||||||
|
selectNext()
|
||||||
|
event.accepted = true
|
||||||
|
} else if ((event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier)) ||
|
||||||
|
(event.key === Qt.Key_Up)) {
|
||||||
|
selectPrevious()
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier)) {
|
||||||
|
createNewSession()
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_R && (event.modifiers & Qt.ControlModifier)) {
|
||||||
|
if (MuxService.supportsRename && selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||||
|
renameSession(filteredSessions[selectedIndex].name)
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_D && (event.modifiers & Qt.ControlModifier)) {
|
||||||
|
if (selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||||
|
killSession(filteredSessions[selectedIndex].name)
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_Escape) {
|
||||||
|
hide()
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
|
activateSelected()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
height: parent.height - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
y: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
// Header
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: I18n.tr("%1 Sessions").arg(MuxService.displayName)
|
||||||
|
font.pixelSize: Theme.fontSizeLarge + 4
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: I18n.tr("%1 active, %2 filtered").arg(MuxService.sessions.length).arg(muxModal.filteredSessions.length)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search field
|
||||||
|
DankTextField {
|
||||||
|
id: searchField
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
cornerRadius: Theme.cornerRadius
|
||||||
|
backgroundColor: Theme.surfaceContainerHigh
|
||||||
|
normalBorderColor: Theme.outlineMedium
|
||||||
|
focusedBorderColor: Theme.primary
|
||||||
|
leftIconName: "search"
|
||||||
|
leftIconSize: Theme.iconSize
|
||||||
|
leftIconColor: Theme.surfaceVariantText
|
||||||
|
leftIconFocusedColor: Theme.primary
|
||||||
|
showClearButton: true
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
placeholderText: I18n.tr("Search sessions...")
|
||||||
|
keyForwardTargets: [muxPanel]
|
||||||
|
|
||||||
|
onTextEdited: {
|
||||||
|
muxModal.searchText = text
|
||||||
|
muxModal.selectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Session Button
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 56
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: muxModal.selectedIndex === -1 ? Theme.primaryContainer :
|
||||||
|
(newMouse.containsMouse ? Theme.surfaceContainerHigh : Theme.surfaceContainer)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 40
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
radius: 20
|
||||||
|
color: Theme.primaryContainer
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "add"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("New Session")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Create a new %1 session (n)").arg(MuxService.displayName)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: newMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: muxModal.createNewSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sessions List
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 88 - 48 - shortcutsBar.height - Theme.spacingS * 3
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ScriptModel {
|
||||||
|
values: muxModal.filteredSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 64
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: muxModal.selectedIndex === index ? Theme.primaryContainer :
|
||||||
|
(sessionMouse.containsMouse ? Theme.surfaceContainerHigh : "transparent")
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: sessionMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: muxModal.attachToSession(modelData.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
// Avatar
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 40
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
radius: 20
|
||||||
|
color: modelData.attached ? Theme.primaryContainer : Theme.surfaceContainerHigh
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData.name.charAt(0).toUpperCase()
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: modelData.attached ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info
|
||||||
|
Column {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
var parts = []
|
||||||
|
if (modelData.windows !== "N/A")
|
||||||
|
parts.push(I18n.tr("%1 windows").arg(modelData.windows))
|
||||||
|
parts.push(modelData.attached ? I18n.tr("attached") : I18n.tr("detached"))
|
||||||
|
return parts.join(" \u2022 ")
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename button (tmux only)
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
radius: 18
|
||||||
|
visible: MuxService.supportsRename
|
||||||
|
color: renameMouse.containsMouse ? Theme.surfaceContainerHighest : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "edit"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: renameMouse.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: renameMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: muxModal.renameSession(modelData.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete button
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
radius: 18
|
||||||
|
color: deleteMouse.containsMouse ? Theme.errorContainer : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "delete"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: deleteMouse.containsMouse ? Theme.error : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: deleteMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
muxModal.killSession(modelData.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: muxModal.filteredSessions.length === 0 ? 200 : 0
|
||||||
|
visible: muxModal.filteredSessions.length === 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: muxModal.searchText.length > 0 ? "search_off" : "terminal"
|
||||||
|
size: 48
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: muxModal.searchText.length > 0 ? I18n.tr("No sessions found") : I18n.tr("No active %1 sessions").arg(MuxService.displayName)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: muxModal.searchText.length > 0 ? I18n.tr("Try a different search") : I18n.tr("Press 'n' or click 'New Session' to create one")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcuts bar
|
||||||
|
Row {
|
||||||
|
id: shortcutsBar
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
bottomPadding: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
var shortcuts = [
|
||||||
|
{ key: "↑↓", label: I18n.tr("Navigate") },
|
||||||
|
{ key: "↵", label: I18n.tr("Attach") },
|
||||||
|
{ key: "^N", label: I18n.tr("New") },
|
||||||
|
{ key: "^D", label: I18n.tr("Kill") },
|
||||||
|
{ key: "Esc", label: I18n.tr("Close") }
|
||||||
|
]
|
||||||
|
if (MuxService.supportsRename)
|
||||||
|
shortcuts.splice(3, 0, { key: "^R", label: I18n.tr("Rename") })
|
||||||
|
return shortcuts
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Row {
|
||||||
|
required property var modelData
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: keyText.width + Theme.spacingS
|
||||||
|
height: keyText.height + 4
|
||||||
|
radius: 4
|
||||||
|
color: Theme.surfaceContainerHighest
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: keyText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData.key
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.label
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -503,5 +503,20 @@ FocusScope {
|
|||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: muxLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 30
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: MuxTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,12 @@ Rectangle {
|
|||||||
"tabIndex": 8,
|
"tabIndex": 8,
|
||||||
"cupsOnly": true
|
"cupsOnly": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "multiplexers",
|
||||||
|
"text": I18n.tr("Multiplexers"),
|
||||||
|
"icon": "terminal",
|
||||||
|
"tabIndex": 30
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "window_rules",
|
"id": "window_rules",
|
||||||
"text": I18n.tr("Window Rules"),
|
"text": I18n.tr("Window Rules"),
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ DankPopout {
|
|||||||
|
|
||||||
backgroundInteractive: !anyModalOpen
|
backgroundInteractive: !anyModalOpen
|
||||||
|
|
||||||
|
onCredentialsPromptOpenChanged: {
|
||||||
|
if (credentialsPromptOpen && shouldBeVisible)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPolkitModalOpenChanged: {
|
||||||
|
if (polkitModalOpen && shouldBeVisible)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
customKeyboardFocus: {
|
customKeyboardFocus: {
|
||||||
if (!shouldBeVisible)
|
if (!shouldBeVisible)
|
||||||
return WlrKeyboardFocus.None;
|
return WlrKeyboardFocus.None;
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -312,7 +312,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -336,7 +336,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -360,7 +360,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -686,6 +686,7 @@ PanelWindow {
|
|||||||
onHasActivePopoutChanged: evaluateReveal()
|
onHasActivePopoutChanged: evaluateReveal()
|
||||||
|
|
||||||
function updateActivePopoutState() {
|
function updateActivePopoutState() {
|
||||||
|
if (!barWindow.screen) return;
|
||||||
const screenName = barWindow.screen.name;
|
const screenName = barWindow.screen.name;
|
||||||
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
||||||
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -38,12 +39,20 @@ BasePill {
|
|||||||
property var _vAudio: null
|
property var _vAudio: null
|
||||||
property var _vBrightness: null
|
property var _vBrightness: null
|
||||||
property var _vMic: null
|
property var _vMic: null
|
||||||
|
property var _interactionDelegates: []
|
||||||
|
readonly property var defaultControlCenterGroupOrder: ["network", "vpn", "bluetooth", "audio", "microphone", "brightness", "battery", "printer", "screenSharing"]
|
||||||
|
readonly property var effectiveControlCenterGroupOrder: getEffectiveControlCenterGroupOrder()
|
||||||
|
readonly property var controlCenterRenderModel: getControlCenterRenderModel()
|
||||||
|
|
||||||
|
onIsVerticalOrientationChanged: root.clearInteractionRefs()
|
||||||
|
|
||||||
onWheel: function (wheelEvent) {
|
onWheel: function (wheelEvent) {
|
||||||
const delta = wheelEvent.angleDelta.y;
|
const delta = wheelEvent.angleDelta.y;
|
||||||
if (delta === 0)
|
if (delta === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
root.refreshInteractionRefs();
|
||||||
|
|
||||||
const rootX = wheelEvent.x - root.leftMargin;
|
const rootX = wheelEvent.x - root.leftMargin;
|
||||||
const rootY = wheelEvent.y - root.topMargin;
|
const rootY = wheelEvent.y - root.topMargin;
|
||||||
|
|
||||||
@@ -72,6 +81,8 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: function (rootX, rootY) {
|
onRightClicked: function (rootX, rootY) {
|
||||||
|
root.refreshInteractionRefs();
|
||||||
|
|
||||||
if (root.isVerticalOrientation && _vCol) {
|
if (root.isVerticalOrientation && _vCol) {
|
||||||
const pos = root.mapToItem(_vCol, rootX, rootY);
|
const pos = root.mapToItem(_vCol, rootX, rootY);
|
||||||
if (_vAudio?.visible && pos.y >= _vAudio.y && pos.y < _vAudio.y + _vAudio.height) {
|
if (_vAudio?.visible && pos.y >= _vAudio.y && pos.y < _vAudio.y + _vAudio.height) {
|
||||||
@@ -279,26 +290,142 @@ BasePill {
|
|||||||
return CupsService.getTotalJobsNum() > 0;
|
return CupsService.getTotalJobsNum() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getControlCenterIconSize() {
|
||||||
|
return Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEffectiveControlCenterGroupOrder() {
|
||||||
|
const knownIds = root.defaultControlCenterGroupOrder;
|
||||||
|
const savedOrder = root.widgetData?.controlCenterGroupOrder;
|
||||||
|
const result = [];
|
||||||
|
const seen = {};
|
||||||
|
|
||||||
|
if (savedOrder && typeof savedOrder.length === "number") {
|
||||||
|
for (let i = 0; i < savedOrder.length; ++i) {
|
||||||
|
const groupId = savedOrder[i];
|
||||||
|
if (knownIds.indexOf(groupId) === -1 || seen[groupId])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
seen[groupId] = true;
|
||||||
|
result.push(groupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < knownIds.length; ++i) {
|
||||||
|
const groupId = knownIds[i];
|
||||||
|
if (seen[groupId])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
seen[groupId] = true;
|
||||||
|
result.push(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGroupVisible(groupId) {
|
||||||
|
switch (groupId) {
|
||||||
|
case "screenSharing":
|
||||||
|
return root.showScreenSharingIcon && NiriService.hasCasts;
|
||||||
|
case "network":
|
||||||
|
return root.showNetworkIcon && NetworkService.networkAvailable;
|
||||||
|
case "vpn":
|
||||||
|
return root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected;
|
||||||
|
case "bluetooth":
|
||||||
|
return root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled;
|
||||||
|
case "audio":
|
||||||
|
return root.showAudioIcon;
|
||||||
|
case "microphone":
|
||||||
|
return root.showMicIcon;
|
||||||
|
case "brightness":
|
||||||
|
return root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice();
|
||||||
|
case "battery":
|
||||||
|
return root.showBatteryIcon && BatteryService.batteryAvailable;
|
||||||
|
case "printer":
|
||||||
|
return root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompositeGroup(groupId) {
|
||||||
|
return groupId === "audio" || groupId === "microphone" || groupId === "brightness";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getControlCenterRenderModel() {
|
||||||
|
return root.effectiveControlCenterGroupOrder.map(groupId => ({
|
||||||
|
"id": groupId,
|
||||||
|
"visible": root.isGroupVisible(groupId),
|
||||||
|
"composite": root.isCompositeGroup(groupId)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInteractionRefs() {
|
||||||
|
root._hAudio = null;
|
||||||
|
root._hBrightness = null;
|
||||||
|
root._hMic = null;
|
||||||
|
root._vAudio = null;
|
||||||
|
root._vBrightness = null;
|
||||||
|
root._vMic = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerInteractionDelegate(isVertical, item) {
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
||||||
|
const entry = root._interactionDelegates[i];
|
||||||
|
if (entry && entry.item === item) {
|
||||||
|
entry.isVertical = isVertical;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root._interactionDelegates = root._interactionDelegates.concat([
|
||||||
|
{
|
||||||
|
"isVertical": isVertical,
|
||||||
|
"item": item
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterInteractionDelegate(item) {
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
root._interactionDelegates = root._interactionDelegates.filter(entry => entry && entry.item !== item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshInteractionRefs() {
|
||||||
|
root.clearInteractionRefs();
|
||||||
|
|
||||||
|
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
||||||
|
const entry = root._interactionDelegates[i];
|
||||||
|
const item = entry?.item;
|
||||||
|
if (!item || !item.visible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const groupId = item.interactionGroupId;
|
||||||
|
if (entry.isVertical) {
|
||||||
|
if (groupId === "audio")
|
||||||
|
root._vAudio = item;
|
||||||
|
else if (groupId === "microphone")
|
||||||
|
root._vMic = item;
|
||||||
|
else if (groupId === "brightness")
|
||||||
|
root._vBrightness = item;
|
||||||
|
} else {
|
||||||
|
if (groupId === "audio")
|
||||||
|
root._hAudio = item;
|
||||||
|
else if (groupId === "microphone")
|
||||||
|
root._hMic = item;
|
||||||
|
else if (groupId === "brightness")
|
||||||
|
root._hBrightness = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hasNoVisibleIcons() {
|
function hasNoVisibleIcons() {
|
||||||
if (root.showScreenSharingIcon && NiriService.hasCasts)
|
return !root.controlCenterRenderModel.some(entry => entry.visible);
|
||||||
return false;
|
|
||||||
if (root.showNetworkIcon && NetworkService.networkAvailable)
|
|
||||||
return false;
|
|
||||||
if (root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected)
|
|
||||||
return false;
|
|
||||||
if (root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled)
|
|
||||||
return false;
|
|
||||||
if (root.showAudioIcon)
|
|
||||||
return false;
|
|
||||||
if (root.showMicIcon)
|
|
||||||
return false;
|
|
||||||
if (root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice())
|
|
||||||
return false;
|
|
||||||
if (root.showBatteryIcon && BatteryService.batteryAvailable)
|
|
||||||
return false;
|
|
||||||
if (root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs())
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
@@ -309,12 +436,7 @@ BasePill {
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
root._hRow = controlIndicators;
|
root._hRow = controlIndicators;
|
||||||
root._vCol = controlColumn;
|
root._vCol = controlColumn;
|
||||||
root._hAudio = audioIcon.parent;
|
root.clearInteractionRefs();
|
||||||
root._hBrightness = brightnessIcon.parent;
|
|
||||||
root._hMic = micIcon.parent;
|
|
||||||
root._vAudio = audioIconV.parent;
|
|
||||||
root._vBrightness = brightnessIconV.parent;
|
|
||||||
root._vMic = micIconV.parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -324,162 +446,151 @@ BasePill {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
Item {
|
Repeater {
|
||||||
width: parent.width
|
model: root.controlCenterRenderModel
|
||||||
height: root.vIconSize
|
Item {
|
||||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
id: verticalGroupItem
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
property string interactionGroupId: modelData.id
|
||||||
|
|
||||||
DankIcon {
|
width: parent.width
|
||||||
name: "screen_record"
|
height: {
|
||||||
size: root.vIconSize
|
switch (modelData.id) {
|
||||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
case "audio":
|
||||||
anchors.centerIn: parent
|
return root.vIconSize + (audioPercentV.visible ? audioPercentV.implicitHeight + 2 : 0);
|
||||||
}
|
case "microphone":
|
||||||
}
|
return root.vIconSize + (micPercentV.visible ? micPercentV.implicitHeight + 2 : 0);
|
||||||
|
case "brightness":
|
||||||
|
return root.vIconSize + (brightnessPercentV.visible ? brightnessPercentV.implicitHeight + 2 : 0);
|
||||||
|
default:
|
||||||
|
return root.vIconSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible: modelData.visible
|
||||||
|
|
||||||
Item {
|
Component.onCompleted: {
|
||||||
width: parent.width
|
root.registerInteractionDelegate(true, verticalGroupItem);
|
||||||
height: root.vIconSize
|
root.refreshInteractionRefs();
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (root) {
|
||||||
|
root.unregisterInteractionDelegate(verticalGroupItem);
|
||||||
|
root.refreshInteractionRefs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVisibleChanged: root.refreshInteractionRefs()
|
||||||
|
onInteractionGroupIdChanged: {
|
||||||
|
root.refreshInteractionRefs();
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.getNetworkIconName()
|
anchors.centerIn: parent
|
||||||
size: root.vIconSize
|
visible: !verticalGroupItem.modelData.composite
|
||||||
color: root.getNetworkIconColor()
|
name: {
|
||||||
anchors.centerIn: parent
|
switch (verticalGroupItem.modelData.id) {
|
||||||
}
|
case "screenSharing":
|
||||||
}
|
return "screen_record";
|
||||||
|
case "network":
|
||||||
|
return root.getNetworkIconName();
|
||||||
|
case "vpn":
|
||||||
|
return "vpn_lock";
|
||||||
|
case "bluetooth":
|
||||||
|
return "bluetooth";
|
||||||
|
case "battery":
|
||||||
|
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
||||||
|
case "printer":
|
||||||
|
return "print";
|
||||||
|
default:
|
||||||
|
return "settings";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size: root.vIconSize
|
||||||
|
color: {
|
||||||
|
switch (verticalGroupItem.modelData.id) {
|
||||||
|
case "screenSharing":
|
||||||
|
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "network":
|
||||||
|
return root.getNetworkIconColor();
|
||||||
|
case "vpn":
|
||||||
|
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "bluetooth":
|
||||||
|
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "battery":
|
||||||
|
return root.getBatteryIconColor();
|
||||||
|
case "printer":
|
||||||
|
return Theme.primary;
|
||||||
|
default:
|
||||||
|
return Theme.widgetIconColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
DankIcon {
|
||||||
width: parent.width
|
id: audioIconV
|
||||||
height: root.vIconSize
|
visible: verticalGroupItem.modelData.id === "audio"
|
||||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
name: root.getVolumeIconName()
|
||||||
|
size: root.vIconSize
|
||||||
|
color: Theme.widgetIconColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
NumericText {
|
||||||
name: "vpn_lock"
|
id: audioPercentV
|
||||||
size: root.vIconSize
|
visible: verticalGroupItem.modelData.id === "audio" && root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
anchors.centerIn: parent
|
reserveText: "100%"
|
||||||
}
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
}
|
color: Theme.widgetTextColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: audioIconV.bottom
|
||||||
|
anchors.topMargin: 2
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
DankIcon {
|
||||||
width: parent.width
|
id: micIconV
|
||||||
height: root.vIconSize
|
visible: verticalGroupItem.modelData.id === "microphone"
|
||||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
name: root.getMicIconName()
|
||||||
|
size: root.vIconSize
|
||||||
|
color: root.getMicIconColor()
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
NumericText {
|
||||||
name: "bluetooth"
|
id: micPercentV
|
||||||
size: root.vIconSize
|
visible: verticalGroupItem.modelData.id === "microphone" && root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
anchors.centerIn: parent
|
reserveText: "100%"
|
||||||
}
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
}
|
color: Theme.widgetTextColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: micIconV.bottom
|
||||||
|
anchors.topMargin: 2
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
DankIcon {
|
||||||
width: parent.width
|
id: brightnessIconV
|
||||||
height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0)
|
visible: verticalGroupItem.modelData.id === "brightness"
|
||||||
visible: root.showAudioIcon
|
name: root.getBrightnessIconName()
|
||||||
|
size: root.vIconSize
|
||||||
|
color: Theme.widgetIconColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
NumericText {
|
||||||
id: audioIconV
|
id: brightnessPercentV
|
||||||
name: root.getVolumeIconName()
|
visible: verticalGroupItem.modelData.id === "brightness" && root.showBrightnessPercent && isFinite(getBrightness())
|
||||||
size: root.vIconSize
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
color: Theme.widgetIconColor
|
reserveText: "100%"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
anchors.top: parent.top
|
color: Theme.widgetTextColor
|
||||||
}
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: brightnessIconV.bottom
|
||||||
NumericText {
|
anchors.topMargin: 2
|
||||||
id: audioPercentV
|
}
|
||||||
visible: root.showAudioPercent
|
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: audioIconV.bottom
|
|
||||||
anchors.topMargin: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0)
|
|
||||||
visible: root.showMicIcon
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: micIconV
|
|
||||||
name: root.getMicIconName()
|
|
||||||
size: root.vIconSize
|
|
||||||
color: root.getMicIconColor()
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
}
|
|
||||||
|
|
||||||
NumericText {
|
|
||||||
id: micPercentV
|
|
||||||
visible: root.showMicPercent
|
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: micIconV.bottom
|
|
||||||
anchors.topMargin: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0)
|
|
||||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: brightnessIconV
|
|
||||||
name: root.getBrightnessIconName()
|
|
||||||
size: root.vIconSize
|
|
||||||
color: Theme.widgetIconColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
}
|
|
||||||
|
|
||||||
NumericText {
|
|
||||||
id: brightnessPercentV
|
|
||||||
visible: root.showBrightnessPercent
|
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: brightnessIconV.bottom
|
|
||||||
anchors.topMargin: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: root.vIconSize
|
|
||||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
|
||||||
size: root.vIconSize
|
|
||||||
color: root.getBatteryIconColor()
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: root.vIconSize
|
|
||||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "print"
|
|
||||||
size: root.vIconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,157 +614,206 @@ BasePill {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankIcon {
|
Repeater {
|
||||||
name: "screen_record"
|
model: root.controlCenterRenderModel
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
||||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
Item {
|
||||||
id: networkIcon
|
id: horizontalGroupItem
|
||||||
name: root.getNetworkIconName()
|
required property var modelData
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
required property int index
|
||||||
color: root.getNetworkIconColor()
|
property string interactionGroupId: modelData.id
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
width: {
|
||||||
id: vpnIcon
|
switch (modelData.id) {
|
||||||
name: "vpn_lock"
|
case "audio":
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
return audioGroup.width;
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
case "microphone":
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
return micGroup.width;
|
||||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
case "brightness":
|
||||||
}
|
return brightnessGroup.width;
|
||||||
|
default:
|
||||||
|
return root.getControlCenterIconSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
implicitWidth: width
|
||||||
|
height: root.widgetThickness - root.horizontalPadding * 2
|
||||||
|
visible: modelData.visible
|
||||||
|
|
||||||
DankIcon {
|
Component.onCompleted: {
|
||||||
id: bluetoothIcon
|
root.registerInteractionDelegate(false, horizontalGroupItem);
|
||||||
name: "bluetooth"
|
root.refreshInteractionRefs();
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
}
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
Component.onDestruction: {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
if (root) {
|
||||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
root.unregisterInteractionDelegate(horizontalGroupItem);
|
||||||
}
|
root.refreshInteractionRefs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVisibleChanged: root.refreshInteractionRefs()
|
||||||
|
onInteractionGroupIdChanged: {
|
||||||
|
root.refreshInteractionRefs();
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
DankIcon {
|
||||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4
|
id: iconOnlyItem
|
||||||
implicitWidth: width
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: root.widgetThickness - root.horizontalPadding * 2
|
anchors.left: parent.left
|
||||||
color: "transparent"
|
visible: !horizontalGroupItem.modelData.composite
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
name: {
|
||||||
visible: root.showAudioIcon
|
switch (horizontalGroupItem.modelData.id) {
|
||||||
|
case "screenSharing":
|
||||||
|
return "screen_record";
|
||||||
|
case "network":
|
||||||
|
return root.getNetworkIconName();
|
||||||
|
case "vpn":
|
||||||
|
return "vpn_lock";
|
||||||
|
case "bluetooth":
|
||||||
|
return "bluetooth";
|
||||||
|
case "battery":
|
||||||
|
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
||||||
|
case "printer":
|
||||||
|
return "print";
|
||||||
|
default:
|
||||||
|
return "settings";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size: root.getControlCenterIconSize()
|
||||||
|
color: {
|
||||||
|
switch (horizontalGroupItem.modelData.id) {
|
||||||
|
case "screenSharing":
|
||||||
|
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "network":
|
||||||
|
return root.getNetworkIconColor();
|
||||||
|
case "vpn":
|
||||||
|
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "bluetooth":
|
||||||
|
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
|
||||||
|
case "battery":
|
||||||
|
return root.getBatteryIconColor();
|
||||||
|
case "printer":
|
||||||
|
return Theme.primary;
|
||||||
|
default:
|
||||||
|
return Theme.widgetIconColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
Rectangle {
|
||||||
id: audioIcon
|
id: audioGroup
|
||||||
name: root.getVolumeIconName()
|
width: audioContent.implicitWidth + 2
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
implicitWidth: width
|
||||||
color: Theme.widgetIconColor
|
height: parent.height
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: "transparent"
|
||||||
anchors.left: parent.left
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.leftMargin: 2
|
visible: horizontalGroupItem.modelData.id === "audio"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: audioContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: audioIcon
|
||||||
|
name: root.getVolumeIconName()
|
||||||
|
size: root.getControlCenterIconSize()
|
||||||
|
color: Theme.widgetIconColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericText {
|
||||||
|
id: audioPercent
|
||||||
|
visible: root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||||
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
|
reserveText: "100%"
|
||||||
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
|
color: Theme.widgetTextColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: visible ? implicitWidth : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: micGroup
|
||||||
|
width: micContent.implicitWidth + 2
|
||||||
|
implicitWidth: width
|
||||||
|
height: parent.height
|
||||||
|
color: "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: horizontalGroupItem.modelData.id === "microphone"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: micContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: micIcon
|
||||||
|
name: root.getMicIconName()
|
||||||
|
size: root.getControlCenterIconSize()
|
||||||
|
color: root.getMicIconColor()
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericText {
|
||||||
|
id: micPercent
|
||||||
|
visible: root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||||
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
|
reserveText: "100%"
|
||||||
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
|
color: Theme.widgetTextColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: visible ? implicitWidth : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: brightnessGroup
|
||||||
|
width: brightnessContent.implicitWidth + 2
|
||||||
|
implicitWidth: width
|
||||||
|
height: parent.height
|
||||||
|
color: "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: horizontalGroupItem.modelData.id === "brightness"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: brightnessContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: brightnessIcon
|
||||||
|
name: root.getBrightnessIconName()
|
||||||
|
size: root.getControlCenterIconSize()
|
||||||
|
color: Theme.widgetIconColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericText {
|
||||||
|
id: brightnessPercent
|
||||||
|
visible: root.showBrightnessPercent && isFinite(getBrightness())
|
||||||
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
|
reserveText: "100%"
|
||||||
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
|
color: Theme.widgetTextColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: visible ? implicitWidth : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
|
||||||
id: audioPercent
|
|
||||||
visible: root.showAudioPercent
|
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: audioIcon.right
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
width: reservedWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4
|
|
||||||
implicitWidth: width
|
|
||||||
height: root.widgetThickness - root.horizontalPadding * 2
|
|
||||||
color: "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showMicIcon
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: micIcon
|
|
||||||
name: root.getMicIconName()
|
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
||||||
color: root.getMicIconColor()
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
NumericText {
|
|
||||||
id: micPercent
|
|
||||||
visible: root.showMicPercent
|
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: micIcon.right
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
width: reservedWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.reservedWidth : 0) + 4
|
|
||||||
height: root.widgetThickness - root.horizontalPadding * 2
|
|
||||||
color: "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: brightnessIcon
|
|
||||||
name: root.getBrightnessIconName()
|
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
||||||
color: Theme.widgetIconColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
NumericText {
|
|
||||||
id: brightnessPercent
|
|
||||||
visible: root.showBrightnessPercent
|
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
color: Theme.widgetTextColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: brightnessIcon.right
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
width: reservedWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: batteryIcon
|
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
||||||
color: root.getBatteryIconColor()
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: printerIcon
|
|
||||||
name: "print"
|
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "settings"
|
name: "settings"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
size: root.getControlCenterIconSize()
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.hasNoVisibleIcons()
|
visible: root.hasNoVisibleIcons()
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId);
|
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId);
|
||||||
return workspaceWindows.length > 0 && activeWindow && activeWindow.title;
|
return workspaceWindows.length > 0 && activeWindow && (activeWindow.title || activeWindow.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland) {
|
||||||
if (!Hyprland.focusedWorkspace || !activeWindow || !activeWindow.title) {
|
if (!Hyprland.focusedWorkspace || !activeWindow || !(activeWindow.title || activeWindow.appId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return activeWindow && activeWindow.title;
|
return activeWindow && (activeWindow.title || activeWindow.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
width: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? barThickness : visualWidth) : 0
|
width: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? barThickness : visualWidth) : 0
|
||||||
@@ -212,17 +212,19 @@ BasePill {
|
|||||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||||
const appName = appText.text;
|
const appName = appText.text;
|
||||||
|
|
||||||
if (compactMode && title === appName) {
|
if (compactMode) {
|
||||||
|
if (!title || title === appName)
|
||||||
|
return title || appName;
|
||||||
|
if (title.endsWith(appName))
|
||||||
|
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "") || appName;
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!title || !appName) {
|
if (!title || !appName)
|
||||||
return title;
|
return title;
|
||||||
}
|
|
||||||
|
|
||||||
if (title.endsWith(appName)) {
|
if (title.endsWith(appName))
|
||||||
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ DankPopout {
|
|||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
property int currentTabIndex: 0
|
property int currentTabIndex: 0
|
||||||
|
|
||||||
popupWidth: 700
|
popupWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||||
triggerWidth: 80
|
triggerWidth: 80
|
||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
@@ -168,6 +168,7 @@ DankPopout {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
implicitWidth: Math.max(700, pages.implicitWidth + (Theme.spacingM * 2))
|
||||||
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
focus: true
|
focus: true
|
||||||
@@ -316,6 +317,7 @@ DankPopout {
|
|||||||
id: pages
|
id: pages
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2)
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (root.currentTabIndex === 0)
|
if (root.currentTabIndex === 0)
|
||||||
return overviewLoader.item?.implicitHeight ?? 410;
|
return overviewLoader.item?.implicitHeight ?? 410;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ Item {
|
|||||||
return Math.max(0, Math.min(1, calculatedRatio));
|
return Math.max(0, Math.min(1, calculatedRatio));
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
|
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import qs.Widgets
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
|
|
||||||
property bool showEventDetails: false
|
property bool showEventDetails: false
|
||||||
property date selectedDate: systemClock.date
|
property date selectedDate: systemClock.date
|
||||||
property var selectedDateEvents: []
|
property var selectedDateEvents: []
|
||||||
@@ -41,6 +43,40 @@ Rectangle {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWeekNumber(dateObj) {
|
||||||
|
// Set time to noon to avoid potential Daylight Saving Time related bugs
|
||||||
|
const weekStartDay = startOfWeek(dateObj);
|
||||||
|
weekStartDay.setHours(12, 0, 0, 0);
|
||||||
|
|
||||||
|
let week1Start;
|
||||||
|
|
||||||
|
if (weekStartJs() === 1) {
|
||||||
|
// ISO 8601 Standard, week start on Monday
|
||||||
|
// A week belongs to the year its Thursday falls in
|
||||||
|
// So we have to get the yearTarget from weekStartDay instead of dateObj
|
||||||
|
let yearTarget = weekStartDay;
|
||||||
|
yearTarget.setDate(yearTarget.getDate() + 3); // Monday + 3 = Thursday
|
||||||
|
|
||||||
|
// Week 1 is the week containing Jan 4th
|
||||||
|
const jan4 = new Date(yearTarget.getFullYear(), 0, 4);
|
||||||
|
week1Start = startOfWeek(jan4);
|
||||||
|
} else {
|
||||||
|
// Traditional / US Standard, week start on Sunday
|
||||||
|
// A week belongs to the year its Sunday falls in
|
||||||
|
let yearTarget = weekStartDay;
|
||||||
|
yearTarget.setDate(yearTarget.getDate() + 6); // Monday + 6 = Sunday
|
||||||
|
|
||||||
|
// Week 1 is the week containing Jan 1st
|
||||||
|
const jan1 = new Date(yearTarget.getFullYear(), 0, 1);
|
||||||
|
week1Start = startOfWeek(jan1);
|
||||||
|
}
|
||||||
|
|
||||||
|
week1Start.setHours(12, 0, 0, 0);
|
||||||
|
|
||||||
|
const diffDays = Math.round((weekStartDay.getTime() - week1Start.getTime()) / 86400000); // Number of miliseconds in a day
|
||||||
|
return Math.floor(diffDays / 7) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
function updateSelectedDateEvents() {
|
function updateSelectedDateEvents() {
|
||||||
if (CalendarService && CalendarService.khalAvailable) {
|
if (CalendarService && CalendarService.khalAvailable) {
|
||||||
const events = CalendarService.getEventsForDate(selectedDate);
|
const events = CalendarService.getEventsForDate(selectedDate);
|
||||||
@@ -151,6 +187,7 @@ Rectangle {
|
|||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 28
|
height: 28
|
||||||
@@ -224,120 +261,172 @@ Rectangle {
|
|||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 18
|
height: parent.height - 28 - Theme.spacingS
|
||||||
visible: !showEventDetails
|
visible: !showEventDetails
|
||||||
|
spacing: SettingsData.showWeekNumber ? Theme.spacingS : 0
|
||||||
|
|
||||||
Repeater {
|
Column {
|
||||||
model: {
|
id: weekNumberColumn
|
||||||
const days = [];
|
visible: SettingsData.showWeekNumber
|
||||||
const qtFirst = weekStartQt();
|
width: SettingsData.showWeekNumber ? 28 : 0
|
||||||
for (let i = 0; i < 7; ++i) {
|
height: parent.height
|
||||||
const qtDay = ((qtFirst - 1 + i) % 7) + 1;
|
spacing: Theme.spacingS
|
||||||
days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat));
|
|
||||||
}
|
|
||||||
return days;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
width: parent.width / 7
|
width: parent.width
|
||||||
height: 18
|
height: 18
|
||||||
color: "transparent"
|
}
|
||||||
|
|
||||||
StyledText {
|
Grid {
|
||||||
anchors.centerIn: parent
|
width: parent.width
|
||||||
text: modelData
|
height: parent.height - 18 - Theme.spacingS
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
columns: 1
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
rows: 6
|
||||||
font.weight: Font.Medium
|
|
||||||
|
Repeater {
|
||||||
|
model: 6
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height / 6
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
const rowDate = new Date(calendarGrid.firstDay);
|
||||||
|
rowDate.setDate(rowDate.getDate() + index * 7);
|
||||||
|
return root.getWeekNumber(rowDate);
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Grid {
|
Column {
|
||||||
id: calendarGrid
|
width: SettingsData.showWeekNumber ? (parent.width - weekNumberColumn.width - parent.spacing) : parent.width
|
||||||
visible: !showEventDetails
|
height: parent.height
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
property date displayDate: systemClock.date
|
Row {
|
||||||
property date selectedDate: systemClock.date
|
width: parent.width
|
||||||
|
height: 18
|
||||||
|
|
||||||
readonly property date firstDay: {
|
Repeater {
|
||||||
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
model: {
|
||||||
return startOfWeek(firstOfMonth);
|
const days = [];
|
||||||
}
|
const qtFirst = weekStartQt();
|
||||||
|
for (let i = 0; i < 7; ++i) {
|
||||||
width: parent.width
|
const qtDay = ((qtFirst - 1 + i) % 7) + 1;
|
||||||
height: parent.height - 28 - 18 - Theme.spacingS * 2
|
days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat));
|
||||||
columns: 7
|
}
|
||||||
rows: 6
|
return days;
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 42
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
readonly property date dayDate: {
|
|
||||||
const date = new Date(parent.firstDay);
|
|
||||||
date.setDate(date.getDate() + index);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth()
|
|
||||||
readonly property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
|
||||||
readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString()
|
|
||||||
|
|
||||||
width: parent.width / 7
|
|
||||||
height: parent.height / 6
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Math.min(parent.width - 4, parent.height - 4, 32)
|
|
||||||
height: width
|
|
||||||
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: dayDate.getDate()
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
|
||||||
font.weight: isToday ? Font.Medium : Font.Normal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.bottom: parent.bottom
|
width: parent.width / 7
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
height: 18
|
||||||
anchors.bottomMargin: 4
|
color: "transparent"
|
||||||
width: 12
|
|
||||||
height: 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
|
||||||
color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
|
|
||||||
opacity: isToday ? 0.9 : 0.7
|
|
||||||
|
|
||||||
Behavior on opacity {
|
StyledText {
|
||||||
NumberAnimation {
|
anchors.centerIn: parent
|
||||||
duration: Theme.shortDuration
|
text: modelData
|
||||||
easing.type: Theme.standardEasing
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
id: calendarGrid
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 18 - Theme.spacingS
|
||||||
|
columns: 7
|
||||||
|
rows: 6
|
||||||
|
|
||||||
|
property date displayDate: systemClock.date
|
||||||
|
property date selectedDate: systemClock.date
|
||||||
|
|
||||||
|
readonly property date firstDay: {
|
||||||
|
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
||||||
|
return startOfWeek(firstOfMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 42
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
readonly property date dayDate: {
|
||||||
|
const date = new Date(parent.firstDay);
|
||||||
|
date.setDate(date.getDate() + index);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth()
|
||||||
|
readonly property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
||||||
|
readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString()
|
||||||
|
|
||||||
|
width: parent.width / 7
|
||||||
|
height: parent.height / 6
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(parent.width - 4, parent.height - 4, 32)
|
||||||
|
height: width
|
||||||
|
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: dayDate.getDate()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
font.weight: isToday ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottomMargin: 4
|
||||||
|
width: 12
|
||||||
|
height: 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
||||||
|
color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
|
||||||
|
opacity: isToday ? 0.9 : 0.7
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dayArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) {
|
||||||
|
root.selectedDate = dayDate;
|
||||||
|
root.showEventDetails = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dayArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) {
|
|
||||||
root.selectedDate = dayDate;
|
|
||||||
root.showEventDetails = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankListView {
|
DankListView {
|
||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: parent.height - (showEventDetails ? 40 : 28 + 18) - Theme.spacingS
|
height: parent.height - (showEventDetails ? 40 : 28 + 18) - Theme.spacingS
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
implicitWidth: 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: 410
|
implicitHeight: 410
|
||||||
|
|
||||||
signal switchToWeatherTab
|
signal switchToWeatherTab
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
implicitWidth: 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: 410
|
implicitHeight: 410
|
||||||
|
|
||||||
property string wallpaperDir: ""
|
property string wallpaperDir: ""
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
implicitWidth: 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: 410
|
implicitHeight: 410
|
||||||
property bool syncing: false
|
property bool syncing: false
|
||||||
property bool showHourly: false
|
property bool showHourly: false
|
||||||
|
|||||||
113
quickshell/Modules/Settings/MuxTab.qml
Normal file
113
quickshell/Modules/Settings/MuxTab.qml
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Settings.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property var muxTypeOptions: [
|
||||||
|
"tmux",
|
||||||
|
"zellij"
|
||||||
|
]
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height + Theme.spacingXL
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
topPadding: 4
|
||||||
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "mux"
|
||||||
|
tags: ["mux", "multiplexer", "tmux", "zellij", "type"]
|
||||||
|
title: I18n.tr("Multiplexer")
|
||||||
|
iconName: "terminal"
|
||||||
|
|
||||||
|
SettingsDropdownRow {
|
||||||
|
tab: "mux"
|
||||||
|
tags: ["mux", "multiplexer", "tmux", "zellij", "type", "backend"]
|
||||||
|
settingKey: "muxType"
|
||||||
|
text: I18n.tr("Multiplexer Type")
|
||||||
|
description: I18n.tr("Terminal multiplexer backend to use")
|
||||||
|
options: root.muxTypeOptions
|
||||||
|
currentValue: SettingsData.muxType
|
||||||
|
onValueChanged: value => SettingsData.set("muxType", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "mux"
|
||||||
|
tags: ["mux", "terminal", "custom", "command", "script"]
|
||||||
|
title: I18n.tr("Terminal")
|
||||||
|
iconName: "desktop_windows"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "mux"
|
||||||
|
tags: ["mux", "custom", "command", "override"]
|
||||||
|
settingKey: "muxUseCustomCommand"
|
||||||
|
text: I18n.tr("Use Custom Command")
|
||||||
|
description: I18n.tr("Override terminal with a custom command or script")
|
||||||
|
checked: SettingsData.muxUseCustomCommand
|
||||||
|
onToggled: checked => SettingsData.set("muxUseCustomCommand", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: SettingsData.muxUseCustomCommand
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("The custom command used when attaching to sessions (receives the session name as the first argument)")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.muxCustomCommand
|
||||||
|
placeholderText: I18n.tr("Enter command or script path")
|
||||||
|
onTextEdited: SettingsData.set("muxCustomCommand", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "mux"
|
||||||
|
tags: ["mux", "session", "filter", "exclude", "hide"]
|
||||||
|
title: I18n.tr("Session Filter")
|
||||||
|
iconName: "filter_list"
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Comma-separated list of session names to hide. Wrap in slashes for regex (e.g., /^_.*/).")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.muxSessionFilter
|
||||||
|
placeholderText: I18n.tr("e.g., scratch, /^tmp_.*/, build")
|
||||||
|
onTextEdited: SettingsData.set("muxSessionFilter", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,16 @@ Item {
|
|||||||
settingKey: "dateFormat"
|
settingKey: "dateFormat"
|
||||||
iconName: "calendar_today"
|
iconName: "calendar_today"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "time"
|
||||||
|
tags: ["show", "week"]
|
||||||
|
settingKey: "showWeekNumber"
|
||||||
|
text: I18n.tr("Show Week Number")
|
||||||
|
description: I18n.tr("Show week number in the calendar")
|
||||||
|
checked: SettingsData.showWeekNumber
|
||||||
|
onToggled: checked => SettingsData.set("showWeekNumber", checked)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
tab: "time"
|
tab: "time"
|
||||||
tags: ["first", "day", "week"]
|
tags: ["first", "day", "week"]
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ Item {
|
|||||||
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
|
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
|
||||||
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
|
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
|
||||||
widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon;
|
widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon;
|
||||||
|
widgetObj.controlCenterGroupOrder = ["network", "vpn", "bluetooth", "audio", "microphone", "brightness", "battery", "printer", "screenSharing"];
|
||||||
}
|
}
|
||||||
if (widgetId === "runningApps") {
|
if (widgetId === "runningApps") {
|
||||||
widgetObj.runningAppsCompactMode = SettingsData.runningAppsCompactMode;
|
widgetObj.runningAppsCompactMode = SettingsData.runningAppsCompactMode;
|
||||||
@@ -429,7 +430,7 @@ Item {
|
|||||||
"id": widget.id,
|
"id": widget.id,
|
||||||
"enabled": widget.enabled
|
"enabled": widget.enabled
|
||||||
};
|
};
|
||||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
if (widget[keys[i]] !== undefined)
|
if (widget[keys[i]] !== undefined)
|
||||||
result[keys[i]] = widget[keys[i]];
|
result[keys[i]] = widget[keys[i]];
|
||||||
@@ -498,6 +499,32 @@ Item {
|
|||||||
return;
|
return;
|
||||||
var newWidget = cloneWidgetData(widgets[widgetIndex]);
|
var newWidget = cloneWidgetData(widgets[widgetIndex]);
|
||||||
newWidget[settingName] = value;
|
newWidget[settingName] = value;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
switch (settingName) {
|
||||||
|
case "showAudioIcon":
|
||||||
|
newWidget.showAudioPercent = false;
|
||||||
|
break;
|
||||||
|
case "showMicIcon":
|
||||||
|
newWidget.showMicPercent = false;
|
||||||
|
break;
|
||||||
|
case "showBrightnessIcon":
|
||||||
|
newWidget.showBrightnessPercent = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets[widgetIndex] = newWidget;
|
||||||
|
setWidgetsForSection(sectionId, widgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleControlCenterGroupOrderChanged(sectionId, widgetIndex, groupOrder) {
|
||||||
|
var widgets = getWidgetsForSection(sectionId).slice();
|
||||||
|
if (widgetIndex < 0 || widgetIndex >= widgets.length)
|
||||||
|
return;
|
||||||
|
var previousWidget = widgets[widgetIndex];
|
||||||
|
var newWidget = cloneWidgetData(previousWidget);
|
||||||
|
newWidget.controlCenterGroupOrder = groupOrder.slice();
|
||||||
widgets[widgetIndex] = newWidget;
|
widgets[widgetIndex] = newWidget;
|
||||||
setWidgetsForSection(sectionId, widgets);
|
setWidgetsForSection(sectionId, widgets);
|
||||||
}
|
}
|
||||||
@@ -655,6 +682,8 @@ Item {
|
|||||||
item.showPrinterIcon = widget.showPrinterIcon;
|
item.showPrinterIcon = widget.showPrinterIcon;
|
||||||
if (widget.showScreenSharingIcon !== undefined)
|
if (widget.showScreenSharingIcon !== undefined)
|
||||||
item.showScreenSharingIcon = widget.showScreenSharingIcon;
|
item.showScreenSharingIcon = widget.showScreenSharingIcon;
|
||||||
|
if (widget.controlCenterGroupOrder !== undefined)
|
||||||
|
item.controlCenterGroupOrder = widget.controlCenterGroupOrder;
|
||||||
if (widget.minimumWidth !== undefined)
|
if (widget.minimumWidth !== undefined)
|
||||||
item.minimumWidth = widget.minimumWidth;
|
item.minimumWidth = widget.minimumWidth;
|
||||||
if (widget.showSwap !== undefined)
|
if (widget.showSwap !== undefined)
|
||||||
@@ -948,6 +977,9 @@ Item {
|
|||||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
|
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||||
|
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||||
|
}
|
||||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
@@ -1012,6 +1044,9 @@ Item {
|
|||||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
|
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||||
|
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||||
|
}
|
||||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
@@ -1076,6 +1111,9 @@ Item {
|
|||||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
|
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||||
|
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||||
|
}
|
||||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ Column {
|
|||||||
signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex)
|
signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex)
|
||||||
signal diskMountSelectionChanged(string sectionId, int widgetIndex, string mountPath)
|
signal diskMountSelectionChanged(string sectionId, int widgetIndex, string mountPath)
|
||||||
signal controlCenterSettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
signal controlCenterSettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
||||||
|
signal controlCenterGroupOrderChanged(string sectionId, int widgetIndex, var groupOrder)
|
||||||
signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
||||||
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
|
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
|
||||||
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
|
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
|
||||||
@@ -39,7 +40,7 @@ Column {
|
|||||||
"id": widget.id,
|
"id": widget.id,
|
||||||
"enabled": widget.enabled
|
"enabled": widget.enabled
|
||||||
};
|
};
|
||||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
if (widget[keys[i]] !== undefined)
|
if (widget[keys[i]] !== undefined)
|
||||||
result[keys[i]] = widget[keys[i]];
|
result[keys[i]] = widget[keys[i]];
|
||||||
@@ -90,7 +91,6 @@ Column {
|
|||||||
height: 70
|
height: 70
|
||||||
z: held ? 2 : 1
|
z: held ? 2 : 1
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: itemBackground
|
id: itemBackground
|
||||||
|
|
||||||
@@ -587,6 +587,7 @@ Column {
|
|||||||
controlCenterContextMenu.widgetData = modelData;
|
controlCenterContextMenu.widgetData = modelData;
|
||||||
controlCenterContextMenu.sectionId = root.sectionId;
|
controlCenterContextMenu.sectionId = root.sectionId;
|
||||||
controlCenterContextMenu.widgetIndex = index;
|
controlCenterContextMenu.widgetIndex = index;
|
||||||
|
controlCenterContextMenu.controlCenterGroups = controlCenterContextMenu.getOrderedControlCenterGroups();
|
||||||
|
|
||||||
var buttonPos = ccMenuButton.mapToItem(root, 0, 0);
|
var buttonPos = ccMenuButton.mapToItem(root, 0, 0);
|
||||||
var popupWidth = controlCenterContextMenu.width;
|
var popupWidth = controlCenterContextMenu.width;
|
||||||
@@ -1054,13 +1055,236 @@ Column {
|
|||||||
property string sectionId: ""
|
property string sectionId: ""
|
||||||
property int widgetIndex: -1
|
property int widgetIndex: -1
|
||||||
|
|
||||||
width: 220
|
readonly property real minimumContentWidth: controlCenterContentMetrics.implicitWidth + Theme.spacingS * 2
|
||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
readonly property real controlCenterRowHeight: 32
|
||||||
|
readonly property real controlCenterRowSpacing: 1
|
||||||
|
readonly property real controlCenterGroupVerticalPadding: Theme.spacingXS * 2
|
||||||
|
readonly property real controlCenterMenuSpacing: 2
|
||||||
|
width: Math.max(220, minimumContentWidth)
|
||||||
|
height: getControlCenterPopupHeight(controlCenterGroups)
|
||||||
padding: 0
|
padding: 0
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
cancelControlCenterDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var defaultControlCenterGroups: [
|
||||||
|
{
|
||||||
|
id: "network",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "lan",
|
||||||
|
label: I18n.tr("Network"),
|
||||||
|
setting: "showNetworkIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vpn",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "vpn_lock",
|
||||||
|
label: I18n.tr("VPN"),
|
||||||
|
setting: "showVpnIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "bluetooth",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "bluetooth",
|
||||||
|
label: I18n.tr("Bluetooth"),
|
||||||
|
setting: "showBluetoothIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "audio",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "volume_up",
|
||||||
|
label: I18n.tr("Audio"),
|
||||||
|
setting: "showAudioIcon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "percent",
|
||||||
|
label: I18n.tr("Volume"),
|
||||||
|
setting: "showAudioPercent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "microphone",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "mic",
|
||||||
|
label: I18n.tr("Microphone"),
|
||||||
|
setting: "showMicIcon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "percent",
|
||||||
|
label: I18n.tr("Microphone Volume"),
|
||||||
|
setting: "showMicPercent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "brightness",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "brightness_high",
|
||||||
|
label: I18n.tr("Brightness"),
|
||||||
|
setting: "showBrightnessIcon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "percent",
|
||||||
|
label: I18n.tr("Brightness Value"),
|
||||||
|
setting: "showBrightnessPercent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "battery",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "battery_full",
|
||||||
|
label: I18n.tr("Battery"),
|
||||||
|
setting: "showBatteryIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "printer",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "print",
|
||||||
|
label: I18n.tr("Printer"),
|
||||||
|
setting: "showPrinterIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "screenSharing",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
icon: "screen_record",
|
||||||
|
label: I18n.tr("Screen Sharing"),
|
||||||
|
setting: "showScreenSharingIcon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
property var controlCenterGroups: defaultControlCenterGroups
|
||||||
|
property int draggedControlCenterGroupIndex: -1
|
||||||
|
property int controlCenterGroupDropIndex: -1
|
||||||
|
|
||||||
|
function updateControlCenterGroupDropIndex(draggedIndex, localY) {
|
||||||
|
const totalGroups = controlCenterGroups.length;
|
||||||
|
let dropIndex = totalGroups;
|
||||||
|
|
||||||
|
for (let i = 0; i < totalGroups; i++) {
|
||||||
|
const delegate = groupRepeater.itemAt(i);
|
||||||
|
if (!delegate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const midpoint = delegate.y + delegate.height / 2;
|
||||||
|
if (localY < midpoint) {
|
||||||
|
dropIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controlCenterGroupDropIndex = Math.max(0, Math.min(totalGroups, dropIndex));
|
||||||
|
draggedControlCenterGroupIndex = draggedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishControlCenterDrag() {
|
||||||
|
if (draggedControlCenterGroupIndex < 0) {
|
||||||
|
controlCenterGroupDropIndex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromIndex = draggedControlCenterGroupIndex;
|
||||||
|
let toIndex = controlCenterGroupDropIndex;
|
||||||
|
|
||||||
|
draggedControlCenterGroupIndex = -1;
|
||||||
|
controlCenterGroupDropIndex = -1;
|
||||||
|
|
||||||
|
if (toIndex < 0 || toIndex > controlCenterGroups.length || toIndex === fromIndex || toIndex === fromIndex + 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const groups = controlCenterGroups.slice();
|
||||||
|
const moved = groups.splice(fromIndex, 1)[0];
|
||||||
|
|
||||||
|
if (toIndex > fromIndex)
|
||||||
|
toIndex -= 1;
|
||||||
|
|
||||||
|
groups.splice(toIndex, 0, moved);
|
||||||
|
controlCenterGroups = groups;
|
||||||
|
const reorderedGroupIds = groups.map(group => group.id);
|
||||||
|
root.controlCenterGroupOrderChanged(sectionId, widgetIndex, reorderedGroupIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelControlCenterDrag() {
|
||||||
|
draggedControlCenterGroupIndex = -1;
|
||||||
|
controlCenterGroupDropIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getControlCenterGroupHeight(group) {
|
||||||
|
const rowCount = group?.rows?.length ?? 0;
|
||||||
|
if (rowCount <= 0)
|
||||||
|
return controlCenterGroupVerticalPadding;
|
||||||
|
|
||||||
|
return rowCount * controlCenterRowHeight + Math.max(0, rowCount - 1) * controlCenterRowSpacing + controlCenterGroupVerticalPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getControlCenterPopupHeight(groups) {
|
||||||
|
const orderedGroups = groups || [];
|
||||||
|
let totalHeight = Theme.spacingS * 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < orderedGroups.length; i++) {
|
||||||
|
totalHeight += getControlCenterGroupHeight(orderedGroups[i]);
|
||||||
|
if (i < orderedGroups.length - 1)
|
||||||
|
totalHeight += controlCenterMenuSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrderedControlCenterGroups() {
|
||||||
|
const baseGroups = defaultControlCenterGroups.slice();
|
||||||
|
const currentWidget = contentItem.getCurrentWidgetData();
|
||||||
|
const savedOrder = currentWidget?.controlCenterGroupOrder;
|
||||||
|
if (!savedOrder || !savedOrder.length)
|
||||||
|
return baseGroups;
|
||||||
|
|
||||||
|
const groupMap = {};
|
||||||
|
for (let i = 0; i < baseGroups.length; i++)
|
||||||
|
groupMap[baseGroups[i].id] = baseGroups[i];
|
||||||
|
|
||||||
|
const orderedGroups = [];
|
||||||
|
for (let i = 0; i < savedOrder.length; i++) {
|
||||||
|
const groupId = savedOrder[i];
|
||||||
|
const group = groupMap[groupId];
|
||||||
|
if (group) {
|
||||||
|
orderedGroups.push(group);
|
||||||
|
delete groupMap[groupId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < baseGroups.length; i++) {
|
||||||
|
const group = baseGroups[i];
|
||||||
|
if (groupMap[group.id])
|
||||||
|
orderedGroups.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderedGroups;
|
||||||
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -1069,83 +1293,64 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
|
function getCurrentWidgetData() {
|
||||||
|
const widgets = root.items || [];
|
||||||
|
if (controlCenterContextMenu.widgetIndex >= 0 && controlCenterContextMenu.widgetIndex < widgets.length)
|
||||||
|
return widgets[controlCenterContextMenu.widgetIndex];
|
||||||
|
return controlCenterContextMenu.widgetData;
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: menuColumn
|
id: menuColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
Repeater {
|
Item {
|
||||||
model: [
|
id: controlCenterContentMetrics
|
||||||
{
|
visible: false
|
||||||
icon: "lan",
|
implicitWidth: 16 + Theme.spacingS + 16 + Theme.spacingS + longestControlCenterLabelMetrics.advanceWidth + Theme.spacingM + 40 + Theme.spacingS * 2 + Theme.spacingM
|
||||||
label: I18n.tr("Network"),
|
}
|
||||||
setting: "showNetworkIcon"
|
|
||||||
},
|
TextMetrics {
|
||||||
{
|
id: longestControlCenterLabelMetrics
|
||||||
icon: "vpn_lock",
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
label: I18n.tr("VPN"),
|
text: {
|
||||||
setting: "showVpnIcon"
|
const labels = [
|
||||||
},
|
I18n.tr("Network"),
|
||||||
{
|
I18n.tr("VPN"),
|
||||||
icon: "bluetooth",
|
I18n.tr("Bluetooth"),
|
||||||
label: I18n.tr("Bluetooth"),
|
I18n.tr("Audio"),
|
||||||
setting: "showBluetoothIcon"
|
I18n.tr("Volume"),
|
||||||
},
|
I18n.tr("Microphone"),
|
||||||
{
|
I18n.tr("Microphone Volume"),
|
||||||
icon: "volume_up",
|
I18n.tr("Brightness"),
|
||||||
label: I18n.tr("Audio"),
|
I18n.tr("Brightness Value"),
|
||||||
setting: "showAudioIcon"
|
I18n.tr("Battery"),
|
||||||
},
|
I18n.tr("Printer"),
|
||||||
{
|
I18n.tr("Screen Sharing")
|
||||||
icon: "percent",
|
];
|
||||||
label: I18n.tr("Volume"),
|
let longest = "";
|
||||||
setting: "showAudioPercent"
|
for (let i = 0; i < labels.length; i++) {
|
||||||
},
|
if (labels[i].length > longest.length)
|
||||||
{
|
longest = labels[i];
|
||||||
icon: "mic",
|
}
|
||||||
label: I18n.tr("Microphone"),
|
return longest;
|
||||||
setting: "showMicIcon"
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
icon: "percent",
|
Repeater {
|
||||||
label: I18n.tr("Microphone Volume"),
|
model: controlCenterContextMenu.controlCenterGroups
|
||||||
setting: "showMicPercent"
|
|
||||||
},
|
delegate: Item {
|
||||||
{
|
id: delegateRoot
|
||||||
icon: "brightness_high",
|
|
||||||
label: I18n.tr("Brightness"),
|
|
||||||
setting: "showBrightnessIcon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "percent",
|
|
||||||
label: I18n.tr("Brightness Value"),
|
|
||||||
setting: "showBrightnessPercent"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "battery_full",
|
|
||||||
label: I18n.tr("Battery"),
|
|
||||||
setting: "showBatteryIcon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "print",
|
|
||||||
label: I18n.tr("Printer"),
|
|
||||||
setting: "showPrinterIcon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "screen_record",
|
|
||||||
label: I18n.tr("Screen Sharing"),
|
|
||||||
setting: "showScreenSharingIcon"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
function getCheckedState() {
|
function getCheckedState(settingName) {
|
||||||
var wd = controlCenterContextMenu.widgetData;
|
const wd = controlCenterContextMenu.contentItem.getCurrentWidgetData();
|
||||||
switch (modelData.setting) {
|
switch (settingName) {
|
||||||
case "showNetworkIcon":
|
case "showNetworkIcon":
|
||||||
return wd?.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
return wd?.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||||
case "showVpnIcon":
|
case "showVpnIcon":
|
||||||
@@ -1175,57 +1380,197 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property string rootSetting: modelData.rows[0]?.setting ?? ""
|
||||||
|
readonly property bool rootEnabled: rootSetting ? getCheckedState(rootSetting) : true
|
||||||
|
readonly property bool isDragged: controlCenterContextMenu.draggedControlCenterGroupIndex === index
|
||||||
|
readonly property bool showDropIndicatorAbove: controlCenterContextMenu.controlCenterGroupDropIndex === index
|
||||||
|
readonly property bool showDropIndicatorBelow: controlCenterContextMenu.controlCenterGroupDropIndex === controlCenterContextMenu.controlCenterGroups.length && index === controlCenterContextMenu.controlCenterGroups.length - 1
|
||||||
|
|
||||||
width: menuColumn.width
|
width: menuColumn.width
|
||||||
height: 32
|
height: groupBackground.height
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
Rectangle {
|
||||||
|
id: groupBackground
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: modelData.icon
|
|
||||||
size: 16
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.label
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
id: toggle
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.top: parent.top
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
height: groupContent.implicitHeight + Theme.spacingXS * 2
|
||||||
width: 40
|
radius: Theme.cornerRadius
|
||||||
height: 20
|
color: isDragged ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.18) : (groupHoverArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent")
|
||||||
checked: getCheckedState()
|
opacity: isDragged ? 0.75 : 1.0
|
||||||
onToggled: {
|
|
||||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Rectangle {
|
||||||
id: toggleArea
|
anchors.left: parent.left
|
||||||
anchors.fill: parent
|
anchors.right: parent.right
|
||||||
hoverEnabled: true
|
anchors.top: parent.top
|
||||||
cursorShape: Qt.PointingHandCursor
|
anchors.topMargin: -1
|
||||||
onPressed: {
|
height: 2
|
||||||
toggle.checked = !toggle.checked;
|
radius: 1
|
||||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggle.checked);
|
color: Theme.primary
|
||||||
|
visible: showDropIndicatorAbove
|
||||||
|
z: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: -1
|
||||||
|
height: 2
|
||||||
|
radius: 1
|
||||||
|
color: Theme.primary
|
||||||
|
visible: showDropIndicatorBelow
|
||||||
|
z: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: groupContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Theme.spacingXS
|
||||||
|
implicitHeight: groupColumn.implicitHeight
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: groupColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: groupColumnRepeater
|
||||||
|
model: modelData.rows
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
readonly property var rowData: modelData
|
||||||
|
readonly property bool isFirstRow: index === 0
|
||||||
|
readonly property bool rowEnabled: isFirstRow ? true : delegateRoot.rootEnabled
|
||||||
|
readonly property bool computedCheckedState: rowEnabled ? getCheckedState(rowData.setting) : false
|
||||||
|
readonly property bool rowHovered: rowEnabled && (toggleArea.containsMouse || (isFirstRow && groupDragHandleArea.containsMouse))
|
||||||
|
|
||||||
|
width: groupColumn.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
opacity: rowEnabled ? 1.0 : 0.5
|
||||||
|
color: rowHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.right: toggle.left
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "drag_indicator"
|
||||||
|
size: 16
|
||||||
|
color: groupDragHandleArea.pressed || isDragged ? Theme.primary : Theme.outline
|
||||||
|
visible: isFirstRow
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: groupDragHandleArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
preventStealing: true
|
||||||
|
enabled: isFirstRow
|
||||||
|
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
mouse.accepted = true;
|
||||||
|
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||||
|
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||||
|
}
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (!pressed)
|
||||||
|
return;
|
||||||
|
mouse.accepted = true;
|
||||||
|
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||||
|
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||||
|
}
|
||||||
|
onReleased: mouse => {
|
||||||
|
mouse.accepted = true;
|
||||||
|
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||||
|
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||||
|
controlCenterContextMenu.finishControlCenterDrag();
|
||||||
|
}
|
||||||
|
onCanceled: {
|
||||||
|
controlCenterContextMenu.cancelControlCenterDrag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: rowData.icon
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: rowData.label
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: toggle
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
height: 20
|
||||||
|
enabled: rowEnabled
|
||||||
|
checked: computedCheckedState
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
if (!rowEnabled)
|
||||||
|
return;
|
||||||
|
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, rowData.setting, toggled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: toggleArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 16 + Theme.spacingS * 2
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: rowEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: rowEnabled && controlCenterContextMenu.draggedControlCenterGroupIndex < 0
|
||||||
|
onPressed: {
|
||||||
|
if (!rowEnabled)
|
||||||
|
return;
|
||||||
|
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, rowData.setting, !computedCheckedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: groupHoverArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id: groupRepeater
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
230
quickshell/Services/MuxService.qml
Normal file
230
quickshell/Services/MuxService.qml
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var sessions: []
|
||||||
|
property bool loading: false
|
||||||
|
|
||||||
|
property bool tmuxAvailable: false
|
||||||
|
property bool zellijAvailable: false
|
||||||
|
readonly property bool currentMuxAvailable: muxType === "zellij" ? zellijAvailable : tmuxAvailable
|
||||||
|
|
||||||
|
readonly property string muxType: SettingsData.muxType
|
||||||
|
readonly property string displayName: muxType === "zellij" ? "Zellij" : "Tmux"
|
||||||
|
|
||||||
|
readonly property var terminalFlags: ({
|
||||||
|
"ghostty": ["-e"],
|
||||||
|
"kitty": ["-e"],
|
||||||
|
"alacritty": ["-e"],
|
||||||
|
"foot": [],
|
||||||
|
"wezterm": ["start", "--"],
|
||||||
|
"gnome-terminal": ["--"],
|
||||||
|
"xterm": ["-e"],
|
||||||
|
"konsole": ["-e"],
|
||||||
|
"st": ["-e"],
|
||||||
|
"terminator": ["-e"],
|
||||||
|
"xfce4-terminal": ["-e"]
|
||||||
|
})
|
||||||
|
|
||||||
|
function getTerminalFlag(terminal) {
|
||||||
|
return terminalFlags[terminal] ?? ["-e"]
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property string terminal: Quickshell.env("TERMINAL") || "ghostty"
|
||||||
|
|
||||||
|
function _terminalPrefix() {
|
||||||
|
return [terminal].concat(getTerminalFlag(terminal))
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: tmuxCheckProcess
|
||||||
|
command: ["which", "tmux"]
|
||||||
|
running: false
|
||||||
|
onExited: (code) => { root.tmuxAvailable = (code === 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: zellijCheckProcess
|
||||||
|
command: ["which", "zellij"]
|
||||||
|
running: false
|
||||||
|
onExited: (code) => { root.zellijAvailable = (code === 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAvailability() {
|
||||||
|
tmuxCheckProcess.running = true
|
||||||
|
zellijCheckProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: checkAvailability()
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: listProcess
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
if (root.muxType === "zellij")
|
||||||
|
root._parseZellijSessions(text)
|
||||||
|
else
|
||||||
|
root._parseTmuxSessions(text)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[MuxService] Error parsing sessions:", e)
|
||||||
|
root.sessions = []
|
||||||
|
}
|
||||||
|
root.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr: SplitParser {
|
||||||
|
onRead: (line) => {
|
||||||
|
if (line.trim())
|
||||||
|
console.error("[MuxService] stderr:", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: (code) => {
|
||||||
|
if (code !== 0 && code !== 1) {
|
||||||
|
console.warn("[MuxService] Process exited with code:", code)
|
||||||
|
root.sessions = []
|
||||||
|
}
|
||||||
|
root.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshSessions() {
|
||||||
|
if (!root.currentMuxAvailable) {
|
||||||
|
root.sessions = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
root.loading = true
|
||||||
|
|
||||||
|
if (listProcess.running)
|
||||||
|
listProcess.running = false
|
||||||
|
|
||||||
|
if (root.muxType === "zellij")
|
||||||
|
listProcess.command = ["zellij", "list-sessions", "--no-formatting"]
|
||||||
|
else
|
||||||
|
listProcess.command = ["tmux", "list-sessions", "-F", "#{session_name}|#{session_windows}|#{session_attached}"]
|
||||||
|
|
||||||
|
Qt.callLater(function () {
|
||||||
|
listProcess.running = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isSessionExcluded(name) {
|
||||||
|
var filter = SettingsData.muxSessionFilter.trim()
|
||||||
|
if (filter.length === 0)
|
||||||
|
return false
|
||||||
|
var parts = filter.split(",")
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var pattern = parts[i].trim()
|
||||||
|
if (pattern.length === 0)
|
||||||
|
continue
|
||||||
|
if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2) {
|
||||||
|
try {
|
||||||
|
var re = new RegExp(pattern.slice(1, -1))
|
||||||
|
if (re.test(name))
|
||||||
|
return true
|
||||||
|
} catch (e) {}
|
||||||
|
} else {
|
||||||
|
if (name.toLowerCase() === pattern.toLowerCase())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parseTmuxSessions(output) {
|
||||||
|
var sessionList = []
|
||||||
|
var lines = output.trim().split('\n')
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = lines[i].trim()
|
||||||
|
if (line.length === 0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var parts = line.split('|')
|
||||||
|
if (parts.length >= 3 && !_isSessionExcluded(parts[0])) {
|
||||||
|
sessionList.push({
|
||||||
|
name: parts[0],
|
||||||
|
windows: parts[1],
|
||||||
|
attached: parts[2] === "1"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.sessions = sessionList
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parseZellijSessions(output) {
|
||||||
|
var sessionList = []
|
||||||
|
var lines = output.trim().split('\n')
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = lines[i].trim()
|
||||||
|
if (line.length === 0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var exited = line.includes("(EXITED")
|
||||||
|
var bracketIdx = line.indexOf(" [")
|
||||||
|
var name = (bracketIdx > 0 ? line.substring(0, bracketIdx) : line).trim()
|
||||||
|
|
||||||
|
if (!_isSessionExcluded(name)) {
|
||||||
|
sessionList.push({
|
||||||
|
name: name,
|
||||||
|
windows: "N/A",
|
||||||
|
attached: !exited
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.sessions = sessionList
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachToSession(name) {
|
||||||
|
if (SettingsData.muxUseCustomCommand && SettingsData.muxCustomCommand) {
|
||||||
|
Quickshell.execDetached([SettingsData.muxCustomCommand, name])
|
||||||
|
} else if (root.muxType === "zellij") {
|
||||||
|
Quickshell.execDetached(_terminalPrefix().concat(["zellij", "attach", name]))
|
||||||
|
} else {
|
||||||
|
Quickshell.execDetached(_terminalPrefix().concat(["tmux", "attach", "-t", name]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSession(name) {
|
||||||
|
if (SettingsData.muxUseCustomCommand && SettingsData.muxCustomCommand) {
|
||||||
|
Quickshell.execDetached([SettingsData.muxCustomCommand, name])
|
||||||
|
} else if (root.muxType === "zellij") {
|
||||||
|
Quickshell.execDetached(_terminalPrefix().concat(["zellij", "-s", name]))
|
||||||
|
} else {
|
||||||
|
Quickshell.execDetached(_terminalPrefix().concat(["tmux", "new-session", "-s", name]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool supportsRename: muxType !== "zellij"
|
||||||
|
|
||||||
|
function renameSession(oldName, newName) {
|
||||||
|
if (root.muxType === "zellij")
|
||||||
|
return
|
||||||
|
Quickshell.execDetached(["tmux", "rename-session", "-t", oldName, newName])
|
||||||
|
Qt.callLater(refreshSessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
function killSession(name) {
|
||||||
|
if (root.muxType === "zellij") {
|
||||||
|
Quickshell.execDetached(["zellij", "kill-session", name])
|
||||||
|
} else {
|
||||||
|
Quickshell.execDetached(["tmux", "kill-session", "-t", name])
|
||||||
|
}
|
||||||
|
Qt.callLater(refreshSessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -453,8 +453,8 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
x: contentContainer.x
|
x: contentContainer.x
|
||||||
y: contentContainer.y
|
y: contentContainer.y
|
||||||
width: root.alignedWidth
|
width: shouldBeVisible ? root.alignedWidth : 0
|
||||||
height: root.alignedHeight
|
height: shouldBeVisible ? root.alignedHeight : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -162,6 +162,13 @@ StyledRect {
|
|||||||
if (root.keyForwardTargets[i])
|
if (root.keyForwardTargets[i])
|
||||||
root.keyForwardTargets[i].Keys.pressed(event);
|
root.keyForwardTargets[i].Keys.pressed(event);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier)) && root.keyForwardTargets.length > 0) {
|
||||||
|
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||||
|
if (root.keyForwardTargets[i])
|
||||||
|
root.keyForwardTargets[i].Keys.pressed(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
// M3 disabled track: on surface 12% opacity
|
// Distinguish disabled checked vs unchecked so unchecked disabled switches don't look enabled
|
||||||
color: !toggle.enabled ? Qt.alpha(Theme.surfaceText, 0.12) : (toggle.checked ? Theme.primary : Theme.surfaceVariantAlpha)
|
color: !toggle.enabled ? (toggle.checked ? Qt.alpha(Theme.surfaceText, 0.12) : "transparent") : (toggle.checked ? Theme.primary : Theme.surfaceVariantAlpha)
|
||||||
opacity: toggle.toggling ? 0.6 : 1
|
opacity: toggle.toggling ? 0.6 : 1
|
||||||
|
|
||||||
// M3 disabled unchecked border: on surface 12% opacity
|
// M3 disabled unchecked border: on surface 12% opacity
|
||||||
@@ -119,8 +119,8 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
// M3 disabled thumb:
|
// M3 disabled thumb:
|
||||||
// checked = solid surface | unchecked = on surface 38%
|
// checked = solid surface | unchecked = outlined off-state thumb
|
||||||
color: !toggle.enabled ? (toggle.checked ? Theme.surface : Qt.alpha(Theme.surfaceText, 0.38)) : (toggle.checked ? Theme.surface : Theme.outline)
|
color: !toggle.enabled ? (toggle.checked ? Theme.surface : "transparent") : (toggle.checked ? Theme.surface : Theme.outline)
|
||||||
border.color: !toggle.enabled ? (toggle.checked ? "transparent" : Qt.alpha(Theme.surfaceText, 0.38)) : Theme.outline
|
border.color: !toggle.enabled ? (toggle.checked ? "transparent" : Qt.alpha(Theme.surfaceText, 0.38)) : Theme.outline
|
||||||
border.width: (toggle.checked && toggle.enabled) ? 1 : 2
|
border.width: (toggle.checked && toggle.enabled) ? 1 : 2
|
||||||
|
|
||||||
@@ -165,8 +165,8 @@ Item {
|
|||||||
// M3 disabled icon: on surface 38%
|
// M3 disabled icon: on surface 38%
|
||||||
color: toggle.enabled ? Theme.surfaceText : Qt.alpha(Theme.surfaceText, 0.38)
|
color: toggle.enabled ? Theme.surfaceText : Qt.alpha(Theme.surfaceText, 0.38)
|
||||||
filled: true
|
filled: true
|
||||||
opacity: toggle.checked ? 1 : 0
|
opacity: (toggle.checked && toggle.enabled) ? 1 : 0
|
||||||
scale: toggle.checked ? 1 : 0.6
|
scale: (toggle.checked && toggle.enabled) ? 1 : 0.6
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user