summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/BatteryPopout.qml633
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/DWLLayoutPopout.qml302
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/VpnPopout.qml87
3 files changed, 1022 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/BatteryPopout.qml b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/BatteryPopout.qml
new file mode 100644
index 0000000..f0cb9d8
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/BatteryPopout.qml
@@ -0,0 +1,633 @@
+import QtQuick
+import Quickshell
+import Quickshell.Services.UPower
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+DankPopout {
+ id: root
+
+ layerNamespace: "dms:battery"
+
+ property var triggerScreen: null
+
+ function isActiveProfile(profile) {
+ if (typeof PowerProfiles === "undefined") {
+ return false;
+ }
+
+ return PowerProfiles.profile === profile;
+ }
+
+ function setProfile(profile) {
+ if (typeof PowerProfiles === "undefined") {
+ ToastService.showError(I18n.tr("power-profiles-daemon not available"));
+ return;
+ }
+ PowerProfiles.profile = profile;
+ if (PowerProfiles.profile !== profile) {
+ ToastService.showError(I18n.tr("Failed to set power profile"));
+ }
+ }
+
+ popupWidth: 400
+ popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 0
+ triggerWidth: 70
+ positioning: ""
+ screen: triggerScreen
+
+ onBackgroundClicked: close()
+
+ content: Component {
+ Rectangle {
+ id: batteryContent
+
+ LayoutMirroring.enabled: I18n.isRtl
+ LayoutMirroring.childrenInherit: true
+
+ implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
+ color: "transparent"
+ focus: true
+ Component.onCompleted: {
+ if (root.shouldBeVisible) {
+ forceActiveFocus();
+ }
+ }
+ Keys.onPressed: function (event) {
+ if (event.key === Qt.Key_Escape) {
+ root.close();
+ event.accepted = true;
+ }
+ }
+
+ Connections {
+ function onShouldBeVisibleChanged() {
+ if (root.shouldBeVisible) {
+ Qt.callLater(function () {
+ batteryContent.forceActiveFocus();
+ });
+ }
+ }
+
+ target: root
+ }
+
+ Column {
+ id: contentColumn
+
+ width: parent.width - Theme.spacingL * 2
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingL
+ spacing: Theme.spacingL
+
+ Row {
+ width: parent.width
+ height: 48
+ spacing: Theme.spacingM
+
+ DankIcon {
+ name: {
+ if (!BatteryService.batteryAvailable)
+ return "power";
+
+ if (!BatteryService.isCharging && BatteryService.isPluggedIn) {
+ if (BatteryService.batteryLevel >= 90) {
+ return "battery_charging_full";
+ }
+ if (BatteryService.batteryLevel >= 80) {
+ return "battery_charging_90";
+ }
+ if (BatteryService.batteryLevel >= 60) {
+ return "battery_charging_80";
+ }
+ if (BatteryService.batteryLevel >= 50) {
+ return "battery_charging_60";
+ }
+ if (BatteryService.batteryLevel >= 30) {
+ return "battery_charging_50";
+ }
+ if (BatteryService.batteryLevel >= 20) {
+ return "battery_charging_30";
+ }
+ return "battery_charging_20";
+ }
+ if (BatteryService.isCharging) {
+ if (BatteryService.batteryLevel >= 90) {
+ return "battery_charging_full";
+ }
+ if (BatteryService.batteryLevel >= 80) {
+ return "battery_charging_90";
+ }
+ if (BatteryService.batteryLevel >= 60) {
+ return "battery_charging_80";
+ }
+ if (BatteryService.batteryLevel >= 50) {
+ return "battery_charging_60";
+ }
+ if (BatteryService.batteryLevel >= 30) {
+ return "battery_charging_50";
+ }
+ if (BatteryService.batteryLevel >= 20) {
+ return "battery_charging_30";
+ }
+ return "battery_charging_20";
+ } else {
+ if (BatteryService.batteryLevel >= 95) {
+ return "battery_full";
+ }
+ if (BatteryService.batteryLevel >= 85) {
+ return "battery_6_bar";
+ }
+ if (BatteryService.batteryLevel >= 70) {
+ return "battery_5_bar";
+ }
+ if (BatteryService.batteryLevel >= 55) {
+ return "battery_4_bar";
+ }
+ if (BatteryService.batteryLevel >= 40) {
+ return "battery_3_bar";
+ }
+ if (BatteryService.batteryLevel >= 25) {
+ return "battery_2_bar";
+ }
+ return "battery_1_bar";
+ }
+ }
+ size: Theme.iconSizeLarge
+ color: {
+ if (BatteryService.isLowBattery && !BatteryService.isCharging)
+ return Theme.error;
+ if (BatteryService.isCharging || BatteryService.isPluggedIn)
+ return Theme.primary;
+ return Theme.surfaceText;
+ }
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ id: headerInfoColumn
+ spacing: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2
+ readonly property string timeInfoText: {
+ if (!BatteryService.batteryAvailable)
+ return I18n.tr("Power profile management available");
+ const time = BatteryService.formatTimeRemaining();
+ if (time !== "Unknown") {
+ return BatteryService.isCharging ? I18n.tr("Time until full: %1").arg(time) : I18n.tr("Time remaining: %1").arg(time);
+ }
+ return "";
+ }
+ readonly property bool showPowerRate: BatteryService.batteryAvailable && Math.abs(BatteryService.changeRate) > 0.05
+ readonly property bool isOnAC: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
+ readonly property bool isDischarging: BatteryService.batteryAvailable && !BatteryService.isCharging && !BatteryService.isPluggedIn
+
+ Row {
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : I18n.tr("Power")
+ font.pixelSize: Theme.fontSizeXLarge
+ color: {
+ if (BatteryService.isLowBattery && !BatteryService.isCharging) {
+ return Theme.error;
+ }
+ if (BatteryService.isCharging) {
+ return Theme.primary;
+ }
+ return Theme.surfaceText;
+ }
+ font.weight: Font.Bold
+ }
+
+ StyledText {
+ text: BatteryService.batteryStatus
+ font.pixelSize: Theme.fontSizeLarge
+ color: {
+ if (BatteryService.isLowBattery && !BatteryService.isCharging) {
+ return Theme.error;
+ }
+ if (BatteryService.isCharging) {
+ return Theme.primary;
+ }
+ return Theme.surfaceText;
+ }
+ font.weight: Font.Medium
+ visible: BatteryService.batteryAvailable
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+ visible: headerInfoColumn.timeInfoText.length > 0
+
+ StyledText {
+ id: powerRateText
+ text: `${headerInfoColumn.isOnAC ? "+" : (headerInfoColumn.isDischarging ? "-" : "")}${Math.abs(BatteryService.changeRate).toFixed(1)}W`
+ font.pixelSize: Theme.fontSizeSmall
+ color: {
+ if (headerInfoColumn.isOnAC) {
+ return Theme.primary;
+ }
+ if (headerInfoColumn.isDischarging) {
+ return Theme.warning;
+ }
+ return Theme.surfaceTextMedium;
+ }
+ font.weight: Font.Medium
+ visible: headerInfoColumn.showPowerRate
+ }
+
+ StyledText {
+ text: headerInfoColumn.timeInfoText
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ elide: Text.ElideRight
+ width: parent.width - (powerRateText.visible ? (powerRateText.implicitWidth + parent.spacing) : 0)
+ }
+ }
+ }
+
+ Rectangle {
+ width: 32
+ height: 32
+ radius: 16
+ color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
+ anchors.top: parent.top
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "close"
+ size: Theme.iconSize - 4
+ color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText
+ }
+
+ MouseArea {
+ id: closeBatteryArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onPressed: {
+ root.close();
+ }
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingM
+ visible: BatteryService.batteryAvailable
+
+ StyledRect {
+ width: (parent.width - Theme.spacingM) / 2
+ height: 64
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingXS
+
+ StyledText {
+ text: I18n.tr("Health")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.primary
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: BatteryService.batteryHealth
+ font.pixelSize: Theme.fontSizeLarge
+ color: {
+ if (BatteryService.batteryHealth === "N/A") {
+ return Theme.surfaceText;
+ }
+ const healthNum = parseInt(BatteryService.batteryHealth);
+ return healthNum < 80 ? Theme.error : Theme.surfaceText;
+ }
+ font.weight: Font.Bold
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ StyledRect {
+ width: (parent.width - Theme.spacingM) / 2
+ height: 64
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingXS
+
+ StyledText {
+ text: I18n.tr("Capacity")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.primary
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : I18n.tr("Unknown")
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.surfaceText
+ font.weight: Font.Bold
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ }
+
+ // Individual battery details for multiple batteries
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+ visible: !BatteryService.usePreferred && BatteryService.batteries.length > 1
+
+ StyledText {
+ text: I18n.tr("Individual Batteries")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ font.weight: Font.Medium
+ }
+
+ Repeater {
+ model: ScriptModel {
+ values: BatteryService.batteries
+ }
+
+ delegate: StyledRect {
+ required property var modelData
+ required property int index
+
+ width: parent.width
+ height: batteryColumn.implicitHeight + Theme.spacingM * 2
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ id: batteryColumn
+ anchors.fill: parent
+ anchors.margins: Theme.spacingM
+ spacing: Theme.spacingS
+
+ // Top row: name and percentage
+ Row {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ Column {
+ spacing: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2
+
+ StyledText {
+ text: modelData.model || I18n.tr("Battery %1").arg(index + 1)
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ elide: Text.ElideRight
+ width: parent.width
+ }
+
+ StyledText {
+ text: modelData.nativePath
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ elide: Text.ElideMiddle
+ width: parent.width
+ }
+ }
+
+ Item {
+ width: 1
+ height: parent.height
+ }
+
+ StyledText {
+ id: percentText
+ text: `${Math.round(100 * modelData.percentage)}%`
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ font.weight: Font.Bold
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankIcon {
+ id: chargingIcon
+ name: modelData.state === UPowerDeviceState.Charging ? "bolt" : ""
+ size: Theme.iconSizeSmall
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ visible: modelData.state === UPowerDeviceState.Charging
+ }
+ }
+
+ // Bottom row: Health, Capacity and Time
+ Flow {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ StyledRect {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ height: 48
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 2
+
+ StyledText {
+ text: I18n.tr("Health")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: {
+ if (!modelData.healthSupported || modelData.healthPercentage <= 0)
+ return "N/A";
+ return `${Math.round(modelData.healthPercentage)}%`;
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: {
+ if (!modelData.healthSupported || modelData.healthPercentage <= 0)
+ return Theme.surfaceText;
+ return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText;
+ }
+ font.weight: Font.Bold
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ StyledRect {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ height: 48
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 2
+
+ StyledText {
+ text: I18n.tr("Capacity")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: modelData.energyCapacity > 0 ? `${modelData.energyCapacity.toFixed(1)}` : "N/A"
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ font.weight: Font.Bold
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ StyledRect {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ height: 48
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.width: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 2
+
+ StyledText {
+ text: modelData.state === UPowerDeviceState.Charging ? I18n.tr("To Full") : modelData.state === UPowerDeviceState.Discharging ? I18n.tr("Left") : ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: {
+ const time = modelData.state === UPowerDeviceState.Charging ? modelData.timeToFull : modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0 ? (3600 * modelData.energy) / BatteryService.changeRate : 0;
+
+ if (!time || time <= 0 || time > 86400)
+ return "N/A";
+
+ const hours = Math.floor(time / 3600);
+ const minutes = Math.floor((time % 3600) / 60);
+ return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ font.weight: Font.Bold
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: profileButtonGroup.height * profileButtonGroup.scale
+
+ DankButtonGroup {
+ id: profileButtonGroup
+
+ property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
+ property int currentProfileIndex: {
+ if (typeof PowerProfiles === "undefined")
+ return 1;
+ return profileModel.findIndex(profile => root.isActiveProfile(profile));
+ }
+
+ scale: Math.min(1, parent.width / implicitWidth)
+ transformOrigin: Item.Center
+ anchors.horizontalCenter: parent.horizontalCenter
+ model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
+ currentIndex: currentProfileIndex
+ selectionMode: "single"
+ onSelectionChanged: (index, selected) => {
+ if (!selected)
+ return;
+ root.setProfile(profileModel[index]);
+ }
+ }
+ }
+
+ StyledRect {
+ width: parent.width
+ height: degradationContent.implicitHeight + Theme.spacingL * 2
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
+ border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
+ border.width: 0
+ visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
+
+ Column {
+ id: degradationContent
+ width: parent.width - Theme.spacingL * 2
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingL
+ spacing: Theme.spacingS
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ DankIcon {
+ name: "warning"
+ size: Theme.iconSize
+ color: Theme.error
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ spacing: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - Theme.iconSize - Theme.spacingM
+
+ StyledText {
+ text: I18n.tr("Power Profile Degradation")
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.error
+ font.weight: Font.Medium
+ }
+
+ StyledText {
+ text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
+ wrapMode: Text.WordWrap
+ width: parent.width
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/DWLLayoutPopout.qml b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/DWLLayoutPopout.qml
new file mode 100644
index 0000000..9daed8d
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/DWLLayoutPopout.qml
@@ -0,0 +1,302 @@
+import QtQuick
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+DankPopout {
+ id: root
+
+ layerNamespace: "dms:layout"
+
+ property var triggerScreen: null
+
+ function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
+ triggerX = x;
+ triggerY = y;
+ triggerWidth = width;
+ triggerSection = section;
+ triggerScreen = screen;
+ root.screen = screen;
+
+ storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
+ storedBarSpacing = barSpacing !== undefined ? barSpacing : 4;
+ storedBarConfig = barConfig;
+
+ const pos = barPosition !== undefined ? barPosition : 0;
+ const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0;
+
+ setBarContext(pos, bottomGap);
+
+ updateOutputState();
+ }
+
+ onScreenChanged: updateOutputState()
+
+ function updateOutputState() {
+ if (screen && DwlService.dwlAvailable) {
+ outputState = DwlService.getOutputState(screen.name);
+ } else {
+ outputState = null;
+ }
+ }
+
+ property var outputState: null
+ property string currentLayoutSymbol: outputState?.layoutSymbol || ""
+
+ readonly property var layoutNames: ({
+ "CT": I18n.tr("Center Tiling"),
+ "G": I18n.tr("Grid"),
+ "K": I18n.tr("Deck"),
+ "M": I18n.tr("Monocle"),
+ "RT": I18n.tr("Right Tiling"),
+ "S": I18n.tr("Scrolling"),
+ "T": I18n.tr("Tiling"),
+ "VG": I18n.tr("Vertical Grid"),
+ "VK": I18n.tr("Vertical Deck"),
+ "VS": I18n.tr("Vertical Scrolling"),
+ "VT": I18n.tr("Vertical Tiling")
+ })
+
+ readonly property var layoutIcons: ({
+ "CT": "view_compact",
+ "G": "grid_view",
+ "K": "layers",
+ "M": "fullscreen",
+ "RT": "view_sidebar",
+ "S": "view_carousel",
+ "T": "view_quilt",
+ "VG": "grid_on",
+ "VK": "view_day",
+ "VS": "scrollable_header",
+ "VT": "clarify"
+ })
+
+ function getLayoutName(symbol) {
+ return layoutNames[symbol] || symbol;
+ }
+
+ function getLayoutIcon(symbol) {
+ return layoutIcons[symbol] || "view_quilt";
+ }
+
+ Connections {
+ target: DwlService
+ function onStateChanged() {
+ updateOutputState();
+ }
+ }
+
+ onShouldBeVisibleChanged: {
+ if (shouldBeVisible) {
+ updateOutputState();
+ }
+ }
+
+ Component.onCompleted: {
+ updateOutputState();
+ }
+
+ popupWidth: 300
+ popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 550
+ triggerWidth: 70
+ positioning: ""
+ screen: triggerScreen
+ shouldBeVisible: false
+
+ onBackgroundClicked: close()
+
+ content: Component {
+ Rectangle {
+ id: layoutContent
+
+ implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
+ color: "transparent"
+ focus: true
+
+ Component.onCompleted: {
+ if (root.shouldBeVisible) {
+ forceActiveFocus();
+ }
+ }
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Escape) {
+ root.close();
+ event.accepted = true;
+ }
+ }
+
+ Connections {
+ target: root
+ function onShouldBeVisibleChanged() {
+ if (root.shouldBeVisible) {
+ Qt.callLater(() => {
+ layoutContent.forceActiveFocus();
+ });
+ }
+ }
+ }
+
+ Column {
+ id: contentColumn
+
+ width: parent.width - Theme.spacingL * 2
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingL
+ spacing: Theme.spacingM
+
+ Row {
+ width: parent.width
+ height: 40
+ spacing: Theme.spacingM
+
+ DankIcon {
+ name: "view_quilt"
+ size: Theme.iconSizeLarge
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ spacing: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2
+
+ StyledText {
+ text: I18n.tr("Layout")
+ font.pixelSize: Theme.fontSizeXLarge
+ color: Theme.surfaceText
+ font.weight: Font.Bold
+ }
+
+ StyledText {
+ text: root.currentLayoutSymbol
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ }
+ }
+
+ Rectangle {
+ width: 32
+ height: 32
+ radius: 16
+ color: closeLayoutArea.containsMouse ? Theme.errorHover : "transparent"
+ anchors.top: parent.top
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "close"
+ size: Theme.iconSize - 4
+ color: closeLayoutArea.containsMouse ? Theme.error : Theme.surfaceText
+ }
+
+ MouseArea {
+ id: closeLayoutArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onPressed: {
+ root.close();
+ }
+ }
+ }
+ }
+
+ StyledText {
+ text: I18n.tr("Available Layouts")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ font.weight: Font.Medium
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: DwlService.layouts
+
+ delegate: Rectangle {
+ required property string modelData
+ required property int index
+
+ property bool isActive: modelData === root.currentLayoutSymbol
+
+ width: parent.width
+ height: 40
+ radius: Theme.cornerRadius
+ color: layoutArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : "transparent"
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingS
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: root.getLayoutIcon(modelData)
+ size: 20
+ color: parent.parent.isActive ? Theme.primary : Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: root.getLayoutName(modelData)
+ font.pixelSize: Theme.fontSizeSmall
+ color: parent.parent.parent.isActive ? Theme.primary : Theme.surfaceText
+ font.weight: parent.parent.parent.isActive ? Font.Medium : Font.Normal
+ }
+
+ StyledText {
+ text: modelData
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceTextMedium
+ }
+ }
+ }
+
+ MouseArea {
+ id: layoutArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onPressed: {
+ if (!root.triggerScreen) {
+ return;
+ }
+ if (!DwlService.dwlAvailable) {
+ return;
+ }
+
+ DwlService.setLayout(root.triggerScreen.name, index);
+ root.close();
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+ }
+ }
+
+ StyledText {
+ text: I18n.tr("Right-click bar widget to cycle")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.outline
+ width: parent.width
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/VpnPopout.qml b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/VpnPopout.qml
new file mode 100644
index 0000000..064f578
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/DankBar/Popouts/VpnPopout.qml
@@ -0,0 +1,87 @@
+import QtQuick
+import QtQuick.Layouts
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+DankPopout {
+ id: root
+
+ layerNamespace: "dms:vpn"
+
+ Ref {
+ service: DMSNetworkService
+ }
+
+ property bool wasVisible: false
+ property var triggerScreen: null
+
+ popupWidth: 380
+ popupHeight: Math.min((screen ? screen.height : Screen.height) - 100, contentLoader.item ? contentLoader.item.implicitHeight : 320)
+ triggerWidth: 70
+ screen: triggerScreen
+ shouldBeVisible: false
+
+ onShouldBeVisibleChanged: {
+ if (shouldBeVisible && !wasVisible) {
+ DMSNetworkService.getState();
+ }
+ wasVisible = shouldBeVisible;
+ }
+
+ onBackgroundClicked: close()
+
+ content: Component {
+ Rectangle {
+ id: content
+
+ implicitHeight: contentColumn.height + Theme.spacingL * 2
+ color: "transparent"
+ focus: true
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Escape) {
+ root.close();
+ event.accepted = true;
+ }
+ }
+
+ Column {
+ id: contentColumn
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingL
+ spacing: Theme.spacingM
+
+ RowLayout {
+ width: parent.width
+ height: 32
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: I18n.tr("VPN Connections")
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ Layout.fillWidth: true
+ }
+
+ DankActionButton {
+ iconName: "close"
+ iconSize: Theme.iconSize - 4
+ iconColor: Theme.surfaceText
+ onClicked: root.close()
+ }
+ }
+
+ VpnDetailContent {
+ width: parent.width
+ listHeight: 200
+ parentPopout: root
+ }
+ }
+ }
+ }
+}