| 1 | import QtQuick |
| 2 | import QtQuick.Layouts |
| 3 | import Quickshell |
| 4 | |
| 5 | // The settings app: a normal Hyprland window (class org.quickshell, title "aurora settings"; floated + sized by a |
| 6 | // window rule). Sidebar of sections, one Loader so only the shown section runs (net scans, bt discovery). |
| 7 | // Esc closes, Ctrl+Tab / Ctrl+Shift+Tab (or Ctrl+J/K) cycle sections. Opened with the `settings [section]` IPC. |
| 8 | FloatingWindow { |
| 9 | id: win |
| 10 | property bool opened: false |
| 11 | property string section: "network" |
| 12 | signal closeRequested() |
| 13 | signal sectionRequested(string name) |
| 14 | signal overlayRequested(string name) |
| 15 | |
| 16 | visible: opened |
| 17 | title: "aurora settings" |
| 18 | implicitWidth: 960 |
| 19 | implicitHeight: 680 |
| 20 | color: Theme.bg |
| 21 | // closed by the compositor (killactive): keep shell state in sync |
| 22 | onVisibleChanged: if (!visible && opened) closeRequested() |
| 23 | |
| 24 | readonly property var sections: [ |
| 25 | { id: "network", icon: Theme.iWifi, name: "network", group: "connections" }, |
| 26 | { id: "bluetooth", icon: Theme.iBt, name: "bluetooth", group: "connections" }, |
| 27 | { id: "sound", icon: Theme.iVolHigh, name: "sound", group: "connections" }, |
| 28 | { id: "appearance", icon: Theme.iPalette, name: "appearance", group: "look" }, |
| 29 | { id: "shell", icon: Theme.iDock, name: "shell", group: "look" }, |
| 30 | { id: "windows", icon: Theme.iWindow, name: "windows", group: "hyprland" }, |
| 31 | { id: "input", icon: Theme.iMouse, name: "input", group: "hyprland" }, |
| 32 | { id: "keybinds", icon: Theme.iKeyboard, name: "keybinds", group: "hyprland" }, |
| 33 | { id: "display", icon: Theme.iMonitor, name: "display", group: "system" }, |
| 34 | { id: "power", icon: Theme.iPower, name: "power", group: "system" }, |
| 35 | { id: "about", icon: Theme.iArch, name: "about", group: "system" } |
| 36 | ] |
| 37 | readonly property int index: { |
| 38 | for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i |
| 39 | return 0 |
| 40 | } |
| 41 | function cycle(d) { sectionRequested(sections[(index + d + sections.length) % sections.length].id) } |
| 42 | |
| 43 | Item { |
| 44 | anchors.fill: parent |
| 45 | focus: true |
| 46 | Keys.onPressed: e => { |
| 47 | var ctrl = e.modifiers & Qt.ControlModifier |
| 48 | if (e.key === Qt.Key_Escape) { win.closeRequested(); e.accepted = true } |
| 49 | else if (ctrl && (e.key === Qt.Key_Tab || e.key === Qt.Key_J)) { win.cycle(1); e.accepted = true } |
| 50 | else if (ctrl && (e.key === Qt.Key_Backtab || e.key === Qt.Key_K)) { win.cycle(-1); e.accepted = true } |
| 51 | } |
| 52 | |
| 53 | RowLayout { |
| 54 | anchors.fill: parent |
| 55 | spacing: 0 |
| 56 | |
| 57 | // sidebar |
| 58 | Rectangle { |
| 59 | Layout.fillHeight: true |
| 60 | Layout.preferredWidth: 200 |
| 61 | color: Theme.surface |
| 62 | ColumnLayout { |
| 63 | anchors.fill: parent |
| 64 | anchors.margins: 10 |
| 65 | spacing: 2 |
| 66 | Text { |
| 67 | Layout.bottomMargin: 8 |
| 68 | text: Theme.iCog + " aurora settings" |
| 69 | color: Theme.accent |
| 70 | font.family: Theme.font |
| 71 | font.pixelSize: Theme.fontSize + 1 |
| 72 | font.bold: true |
| 73 | } |
| 74 | Repeater { |
| 75 | model: win.sections |
| 76 | delegate: ColumnLayout { |
| 77 | id: entry |
| 78 | required property var modelData |
| 79 | required property int index |
| 80 | readonly property bool current: win.section === modelData.id |
| 81 | readonly property bool firstOfGroup: index === 0 || win.sections[index - 1].group !== modelData.group |
| 82 | Layout.fillWidth: true |
| 83 | spacing: 2 |
| 84 | Text { |
| 85 | visible: entry.firstOfGroup |
| 86 | Layout.topMargin: entry.index === 0 ? 0 : 8 |
| 87 | text: entry.modelData.group.toUpperCase() |
| 88 | color: Theme.dim |
| 89 | font.family: Theme.font |
| 90 | font.pixelSize: Theme.fontSize - 3 |
| 91 | font.letterSpacing: 1.5 |
| 92 | } |
| 93 | Rectangle { |
| 94 | Layout.fillWidth: true |
| 95 | implicitHeight: 30 |
| 96 | color: entry.current ? Theme.accent : (em.containsMouse ? Theme.surfaceHi : "transparent") |
| 97 | Text { |
| 98 | anchors.verticalCenter: parent.verticalCenter |
| 99 | x: 10 |
| 100 | text: entry.modelData.icon + " " + entry.modelData.name |
| 101 | color: entry.current ? Theme.bg : Theme.fg |
| 102 | font.family: Theme.font |
| 103 | font.pixelSize: Theme.fontSize |
| 104 | font.bold: entry.current |
| 105 | } |
| 106 | MouseArea { |
| 107 | id: em |
| 108 | anchors.fill: parent |
| 109 | hoverEnabled: true |
| 110 | cursorShape: Qt.PointingHandCursor |
| 111 | onClicked: win.sectionRequested(entry.modelData.id) |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | Item { Layout.fillHeight: true } |
| 117 | } |
| 118 | } |
| 119 | Rectangle { Layout.fillHeight: true; Layout.preferredWidth: 1; color: Theme.line } |
| 120 | |
| 121 | // content |
| 122 | ColumnLayout { |
| 123 | Layout.fillWidth: true |
| 124 | Layout.fillHeight: true |
| 125 | Layout.margins: 16 |
| 126 | spacing: 12 |
| 127 | Text { |
| 128 | text: win.sections[win.index].name.toUpperCase() |
| 129 | color: Theme.fg |
| 130 | font.family: Theme.font |
| 131 | font.pixelSize: Theme.fontSize + 4 |
| 132 | font.bold: true |
| 133 | font.letterSpacing: 2 |
| 134 | } |
| 135 | Loader { |
| 136 | Layout.fillWidth: true |
| 137 | Layout.fillHeight: true |
| 138 | active: win.opened |
| 139 | sourceComponent: { |
| 140 | switch (win.section) { |
| 141 | case "bluetooth": return btC |
| 142 | case "sound": return soundC |
| 143 | case "appearance": return appearanceC |
| 144 | case "shell": return shellC |
| 145 | case "windows": return windowsC |
| 146 | case "input": return inputC |
| 147 | case "keybinds": return keysC |
| 148 | case "display": return displayC |
| 149 | case "power": return powerC |
| 150 | case "about": return aboutC |
| 151 | default: return netC |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | Component { id: netC; NetPage { active: true } } |
| 160 | Component { id: btC; BtPage { active: true } } |
| 161 | Component { id: soundC; AudioPage {} } |
| 162 | Component { id: appearanceC; SetAppearance { onOverlayRequested: name => win.overlayRequested(name) } } |
| 163 | Component { id: shellC; SetShell {} } |
| 164 | Component { id: windowsC; SetWindows {} } |
| 165 | Component { id: inputC; SetInput {} } |
| 166 | Component { id: keysC; SetKeys {} } |
| 167 | Component { id: displayC; SetDisplay {} } |
| 168 | Component { id: powerC; SetPower {} } |
| 169 | Component { id: aboutC; SetAbout {} } |
| 170 | } |