From 3462bcc46ecf74f576ea4397f16492c16bd75b10 Mon Sep 17 00:00:00 2001 From: Nippy Date: Fri, 8 May 2026 19:50:49 +0200 Subject: raveos update --- .../theme-data/dms/Modals/MuxModal.qml | 633 +++++++++++++++++++++ 1 file changed, 633 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/dms/Modals/MuxModal.qml (limited to 'raveos-hyprland-theme/theme-data/dms/Modals/MuxModal.qml') diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/MuxModal.qml b/raveos-hyprland-theme/theme-data/dms/Modals/MuxModal.qml new file mode 100644 index 0000000..9d19388 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Modals/MuxModal.qml @@ -0,0 +1,633 @@ +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: { + const total = MuxService.sessions.length; + const filtered = muxModal.filteredSessions.length; + const activePart = total === 1 + ? I18n.tr("%1 active session").arg(total) + : I18n.tr("%1 active sessions").arg(total); + const filteredPart = filtered === 1 + ? I18n.tr("%1 filtered").arg(filtered) + : I18n.tr("%1 filtered").arg(filtered); + return activePart + ", " + filteredPart; + } + 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(modelData.windows === 1 + ? I18n.tr("%1 window").arg(modelData.windows) + : 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 + } + } + } + } + } + } +} -- cgit v1.3