aurora55505e6
aurora: settings app window (network, bluetooth, sound, appearance, shell)
Replaces the panel's settings tab with a standalone window (SUPER+I, `settings [section]` IPC, cog in the panel header).
+532 −142
16 files changed
- M .config/hypr/source/keybinds.lua +1 −0
- M .config/hypr/source/window_rules.lua +14 −0
- M .config/quickshell/aurora/BtPage.qml +3 −0
- A .config/quickshell/aurora/Choice.qml +36 −0
- M .config/quickshell/aurora/ControlPanel.qml +3 −3
- A .config/quickshell/aurora/Knob.qml +40 −0
- M .config/quickshell/aurora/NetPage.qml +3 −0
- M .config/quickshell/aurora/Notifs.qml +2 −2
- A .config/quickshell/aurora/SetAppearance.qml +111 −0
- A .config/quickshell/aurora/SetShell.qml +73 −0
- M .config/quickshell/aurora/Settings.qml +6 −1
- A .config/quickshell/aurora/SettingsApp.qml +152 −0
- D .config/quickshell/aurora/SettingsPage.qml +0 −134
- A .config/quickshell/aurora/Switch.qml +65 −0
- M .config/quickshell/aurora/Theme.qml +2 −1
- M .config/quickshell/aurora/shell.qml +21 −1
| @@ -39,6 +39,7 @@ hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(menu)) | |||
| 39 | 39 | hl.bind(mainMod .. " + Tab", hl.dsp.window.cycle_next({ next = false }), { repeating = true }) | |
| 40 | 40 | hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd("aurora-chars")) | |
| 41 | 41 | hl.bind(mainMod .. " + escape", hl.dsp.exec_cmd("qs -c aurora ipc call shell power")) | |
| 42 | + | hl.bind(mainMod .. " + I", hl.dsp.exec_cmd("qs -c aurora ipc call shell settings ''"), { description = "aurora settings" }) | |
| 42 | 43 | ||
| 43 | 44 | -- Applications | |
| 44 | 45 | hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(browser)) | |
| @@ -36,3 +36,17 @@ hl.window_rule({ | |||
| 36 | 36 | move = "20 monitor_h-120", | |
| 37 | 37 | float = true, | |
| 38 | 38 | }) | |
| 39 | + | ||
| 40 | + | -- aurora's settings app: a normal window, floating and centred by default | |
| 41 | + | hl.window_rule({ | |
| 42 | + | name = "aurora-settings", | |
| 43 | + | ||
| 44 | + | match = { | |
| 45 | + | class = "^org.quickshell$", | |
| 46 | + | title = "^aurora settings$", | |
| 47 | + | }, | |
| 48 | + | ||
| 49 | + | float = true, | |
| 50 | + | size = "960 680", | |
| 51 | + | center = true, | |
| 52 | + | }) | |
| @@ -12,6 +12,9 @@ ColumnLayout { | |||
| 12 | 12 | onActiveChanged: { | |
| 13 | 13 | if (adapter) adapter.discovering = active && adapter.enabled | |
| 14 | 14 | } | |
| 15 | + | // the settings app creates this page already active and destroys it when leaving the section | |
| 16 | + | Component.onCompleted: if (active && adapter && adapter.enabled) adapter.discovering = true | |
| 17 | + | Component.onDestruction: if (active && adapter) adapter.discovering = false | |
| 15 | 18 | ||
| 16 | 19 | function paired(d) { return d.bonded === true || d.paired === true } | |
| 17 | 20 | ||
| @@ -0,0 +1,36 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // label + segmented buttons. options: [{value, label}] (settings app) | |
| 5 | + | RowLayout { | |
| 6 | + | id: c | |
| 7 | + | property string label: "" | |
| 8 | + | property var options: [] | |
| 9 | + | property var value | |
| 10 | + | signal picked(var v) | |
| 11 | + | Layout.fillWidth: true | |
| 12 | + | spacing: 8 | |
| 13 | + | Text { | |
| 14 | + | Layout.preferredWidth: 150 | |
| 15 | + | text: c.label | |
| 16 | + | color: Theme.fg | |
| 17 | + | elide: Text.ElideRight | |
| 18 | + | font.family: Theme.font | |
| 19 | + | font.pixelSize: Theme.fontSize | |
| 20 | + | } | |
| 21 | + | RowLayout { | |
| 22 | + | Layout.fillWidth: true | |
| 23 | + | spacing: 2 | |
| 24 | + | Repeater { | |
| 25 | + | model: c.options | |
| 26 | + | delegate: IconBtn { | |
| 27 | + | required property var modelData | |
| 28 | + | Layout.fillWidth: true | |
| 29 | + | implicitHeight: 26 | |
| 30 | + | label: modelData.label | |
| 31 | + | active: c.value === modelData.value | |
| 32 | + | onClicked: c.picked(modelData.value) | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | } | |
| 36 | + | } | |
| @@ -11,6 +11,7 @@ PanelWindow { | |||
| 11 | 11 | signal tabRequested(int index) | |
| 12 | 12 | signal closeRequested() | |
| 13 | 13 | signal overlayRequested(string name) | |
| 14 | + | signal settingsRequested() | |
| 14 | 15 | ||
| 15 | 16 | visible: opened | |
| 16 | 17 | anchors { top: true; right: true } | |
| @@ -36,8 +37,7 @@ PanelWindow { | |||
| 36 | 37 | { icon: Theme.iWifi, name: "net" }, | |
| 37 | 38 | { icon: Theme.iBt, name: "bt" }, | |
| 38 | 39 | { icon: Theme.iChip, name: "system" }, | |
| 39 | − | { icon: Theme.iBell, name: "notifs" }, | |
| 40 | − | { icon: Theme.iCog, name: "settings" } | |
| 40 | + | { icon: Theme.iBell, name: "notifs" } | |
| 41 | 41 | ] | |
| 42 | 42 | ||
| 43 | 43 | Rectangle { | |
| @@ -98,6 +98,7 @@ PanelWindow { | |||
| 98 | 98 | } | |
| 99 | 99 | } | |
| 100 | 100 | Item { Layout.fillWidth: true } | |
| 101 | + | IconBtn { icon: Theme.iCog; implicitHeight: 28; onClicked: win.settingsRequested() } | |
| 101 | 102 | IconBtn { icon: Theme.iClose; implicitHeight: 28; onClicked: win.closeRequested() } | |
| 102 | 103 | } | |
| 103 | 104 | ||
| @@ -117,7 +118,6 @@ PanelWindow { | |||
| 117 | 118 | BtPage { active: win.opened && win.tab === 3 } | |
| 118 | 119 | SysPage {} | |
| 119 | 120 | NotifPage {} | |
| 120 | − | SettingsPage {} | |
| 121 | 121 | } | |
| 122 | 122 | } | |
| 123 | 123 | } | |
| @@ -0,0 +1,40 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // label + slider + value, for an integer or real setting (settings app) | |
| 5 | + | RowLayout { | |
| 6 | + | id: k | |
| 7 | + | property string label: "" | |
| 8 | + | property real value: 0 | |
| 9 | + | property real from: 0 | |
| 10 | + | property real to: 1 | |
| 11 | + | property int decimals: 0 | |
| 12 | + | property string unit: "" | |
| 13 | + | signal moved(real v) | |
| 14 | + | Layout.fillWidth: true | |
| 15 | + | spacing: 8 | |
| 16 | + | Text { | |
| 17 | + | Layout.preferredWidth: 150 | |
| 18 | + | text: k.label | |
| 19 | + | color: Theme.fg | |
| 20 | + | elide: Text.ElideRight | |
| 21 | + | font.family: Theme.font | |
| 22 | + | font.pixelSize: Theme.fontSize | |
| 23 | + | } | |
| 24 | + | BarSlider { | |
| 25 | + | Layout.fillWidth: true | |
| 26 | + | from: k.from | |
| 27 | + | to: k.to | |
| 28 | + | step: 1 / Math.max(1, (k.to - k.from) * (k.decimals ? 100 : 1)) | |
| 29 | + | value: k.value | |
| 30 | + | onMoved: v => k.moved(k.decimals ? Math.round(v * 100) / 100 : Math.round(v)) | |
| 31 | + | } | |
| 32 | + | Text { | |
| 33 | + | Layout.preferredWidth: 56 | |
| 34 | + | horizontalAlignment: Text.AlignRight | |
| 35 | + | text: (k.decimals ? k.value.toFixed(k.decimals) : String(Math.round(k.value))) + k.unit | |
| 36 | + | color: Theme.fg | |
| 37 | + | font.family: Theme.font | |
| 38 | + | font.pixelSize: Theme.fontSize | |
| 39 | + | } | |
| 40 | + | } | |
| @@ -5,6 +5,9 @@ ColumnLayout { | |||
| 5 | 5 | id: page | |
| 6 | 6 | property bool active: false | |
| 7 | 7 | onActiveChanged: Net.active = active | |
| 8 | + | // the settings app creates this page already active and destroys it when leaving the section | |
| 9 | + | Component.onCompleted: if (active) Net.active = true | |
| 10 | + | Component.onDestruction: if (active) Net.active = false | |
| 8 | 11 | spacing: 8 | |
| 9 | 12 | ||
| 10 | 13 | // wired / usb tethering (read-only status, plus a disconnect button) | |
| @@ -10,9 +10,9 @@ Singleton { | |||
| 10 | 10 | property bool dnd: false | |
| 11 | 11 | ||
| 12 | 12 | // sound played on every toast (silenced by do-not-disturb, except critical ones) | |
| 13 | − | property bool soundEnabled: true | |
| 13 | + | property bool soundEnabled: Settings.notifSound | |
| 14 | 14 | property string soundFile: Quickshell.env("HOME") + "/.config/quickshell/aurora/sounds/Blow.aiff" | |
| 15 | − | property real soundVolume: 0.7 | |
| 15 | + | property real soundVolume: Settings.notifVolume | |
| 16 | 16 | property var popups: [] // [{n, until}] | |
| 17 | 17 | readonly property var model: server.trackedNotifications | |
| 18 | 18 | readonly property int count: server.trackedNotifications.values.length | |
| @@ -0,0 +1,111 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // Settings app > Appearance: wallpaper, accent, sizes and transparency (all in Settings, applied live). | |
| 5 | + | Flickable { | |
| 6 | + | id: flick | |
| 7 | + | signal overlayRequested(string name) | |
| 8 | + | clip: true | |
| 9 | + | contentWidth: width | |
| 10 | + | contentHeight: col.implicitHeight | |
| 11 | + | boundsBehavior: Flickable.StopAtBounds | |
| 12 | + | ||
| 13 | + | Component.onCompleted: Wall.refresh() | |
| 14 | + | ||
| 15 | + | ColumnLayout { | |
| 16 | + | id: col | |
| 17 | + | width: flick.width | |
| 18 | + | spacing: 10 | |
| 19 | + | ||
| 20 | + | SectionLabel { text: "wallpaper" } | |
| 21 | + | Text { | |
| 22 | + | Layout.fillWidth: true | |
| 23 | + | text: Wall.current !== "" ? Wall.current.replace(Wall.home, "~") : "-" | |
| 24 | + | elide: Text.ElideLeft | |
| 25 | + | color: Theme.dim | |
| 26 | + | font.family: Theme.font | |
| 27 | + | font.pixelSize: Theme.fontSize - 1 | |
| 28 | + | } | |
| 29 | + | RowLayout { | |
| 30 | + | Layout.fillWidth: true | |
| 31 | + | spacing: 6 | |
| 32 | + | IconBtn { | |
| 33 | + | Layout.fillWidth: true | |
| 34 | + | icon: Theme.iImage | |
| 35 | + | label: "open picker" | |
| 36 | + | onClicked: flick.overlayRequested("wall") | |
| 37 | + | } | |
| 38 | + | IconBtn { | |
| 39 | + | Layout.fillWidth: true | |
| 40 | + | icon: Theme.iShuffle | |
| 41 | + | label: "random from folder" | |
| 42 | + | onClicked: Wall.random() | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | Text { | |
| 46 | + | text: "random picks from" | |
| 47 | + | color: Theme.fg | |
| 48 | + | font.family: Theme.font | |
| 49 | + | font.pixelSize: Theme.fontSize | |
| 50 | + | } | |
| 51 | + | Flow { | |
| 52 | + | Layout.fillWidth: true | |
| 53 | + | spacing: 4 | |
| 54 | + | Repeater { | |
| 55 | + | model: [""].concat(Wall.folders) | |
| 56 | + | delegate: IconBtn { | |
| 57 | + | required property string modelData | |
| 58 | + | implicitHeight: 26 | |
| 59 | + | label: modelData === "" ? "all" : modelData | |
| 60 | + | active: Settings.wallFolder === modelData | |
| 61 | + | onClicked: Settings.wallFolder = modelData | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | SectionLabel { text: "accent" } | |
| 67 | + | GridLayout { | |
| 68 | + | Layout.fillWidth: true | |
| 69 | + | columns: 17 | |
| 70 | + | columnSpacing: 4 | |
| 71 | + | rowSpacing: 4 | |
| 72 | + | Repeater { | |
| 73 | + | model: 17 // "auto" + color0..color15 | |
| 74 | + | delegate: Rectangle { | |
| 75 | + | id: sw | |
| 76 | + | required property int index | |
| 77 | + | readonly property int ci: index - 1 // -1 = auto | |
| 78 | + | readonly property bool current: Settings.accentIndex === ci | |
| 79 | + | Layout.fillWidth: true | |
| 80 | + | Layout.preferredHeight: 28 | |
| 81 | + | color: ci < 0 ? Theme.surface : Theme.pal[ci] | |
| 82 | + | border.width: current ? 2 : 1 | |
| 83 | + | border.color: current ? Theme.fg : Theme.line | |
| 84 | + | Text { | |
| 85 | + | anchors.centerIn: parent | |
| 86 | + | visible: sw.ci < 0 || sw.current | |
| 87 | + | text: sw.ci < 0 ? "auto" : Theme.iCheck | |
| 88 | + | color: sw.ci < 0 ? Theme.fg : Theme.bg | |
| 89 | + | font.family: Theme.font | |
| 90 | + | font.pixelSize: Theme.fontSize - 2 | |
| 91 | + | font.bold: true | |
| 92 | + | } | |
| 93 | + | MouseArea { | |
| 94 | + | anchors.fill: parent | |
| 95 | + | cursorShape: Qt.PointingHandCursor | |
| 96 | + | onClicked: Settings.accentIndex = sw.ci | |
| 97 | + | } | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | SectionLabel { text: "sizes" } | |
| 103 | + | Knob { label: "font size"; from: 9; to: 18; value: Settings.fontSize; onMoved: v => Settings.fontSize = v } | |
| 104 | + | Knob { label: "workspace icons"; from: 8; to: 18; value: Settings.wsFontSize; onMoved: v => Settings.wsFontSize = v } | |
| 105 | + | Knob { label: "bar height"; from: 16; to: 36; value: Settings.barHeight; onMoved: v => Settings.barHeight = v } | |
| 106 | + | ||
| 107 | + | SectionLabel { text: "transparency" } | |
| 108 | + | Knob { label: "bar opacity"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v } | |
| 109 | + | Knob { label: "panel opacity"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v } | |
| 110 | + | } | |
| 111 | + | } | |
| @@ -0,0 +1,73 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // Settings app > Shell: desktop widgets, dock, OSD, notifications. | |
| 5 | + | Flickable { | |
| 6 | + | id: flick | |
| 7 | + | clip: true | |
| 8 | + | contentWidth: width | |
| 9 | + | contentHeight: col.implicitHeight | |
| 10 | + | boundsBehavior: Flickable.StopAtBounds | |
| 11 | + | ||
| 12 | + | ColumnLayout { | |
| 13 | + | id: col | |
| 14 | + | width: flick.width | |
| 15 | + | spacing: 8 | |
| 16 | + | ||
| 17 | + | SectionLabel { text: "desktop" } | |
| 18 | + | Switch { label: "clock"; hint: "big clock + stats on the wallpaper"; checked: Settings.desktopClock; onToggled: on => Settings.desktopClock = on } | |
| 19 | + | Switch { label: "visualizer"; hint: "cava bars while music plays"; checked: Settings.desktopViz; onToggled: on => Settings.desktopViz = on } | |
| 20 | + | Knob { label: "visualizer bars"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v } | |
| 21 | + | ||
| 22 | + | SectionLabel { text: "dock" } | |
| 23 | + | Switch { label: "dock"; hint: "autohides at the bottom edge"; checked: Settings.dock; onToggled: on => Settings.dock = on } | |
| 24 | + | Text { | |
| 25 | + | text: "pinned apps (pin more from the dock's right-click)" | |
| 26 | + | color: Theme.dim | |
| 27 | + | font.family: Theme.font | |
| 28 | + | font.pixelSize: Theme.fontSize - 1 | |
| 29 | + | } | |
| 30 | + | Flow { | |
| 31 | + | Layout.fillWidth: true | |
| 32 | + | spacing: 4 | |
| 33 | + | Repeater { | |
| 34 | + | model: Settings.dockPinned | |
| 35 | + | delegate: IconBtn { | |
| 36 | + | required property string modelData | |
| 37 | + | implicitHeight: 26 | |
| 38 | + | icon: Theme.iClose | |
| 39 | + | iconSize: 11 | |
| 40 | + | label: modelData | |
| 41 | + | onClicked: Settings.togglePin(modelData) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | SectionLabel { text: "on-screen display" } | |
| 47 | + | Knob { label: "osd timeout"; unit: "ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 } | |
| 48 | + | ||
| 49 | + | SectionLabel { text: "notifications" } | |
| 50 | + | Switch { label: "do not disturb"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on } | |
| 51 | + | Switch { label: "sound"; checked: Settings.notifSound; onToggled: on => Settings.notifSound = on } | |
| 52 | + | RowLayout { | |
| 53 | + | Layout.fillWidth: true | |
| 54 | + | spacing: 8 | |
| 55 | + | Knob { label: "sound volume"; from: 0; to: 1; decimals: 2; value: Settings.notifVolume; onMoved: v => Settings.notifVolume = v } | |
| 56 | + | IconBtn { icon: Theme.iPlay; implicitHeight: 26; onClicked: Notifs.playSound() } | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | Item { Layout.preferredHeight: 6 } | |
| 60 | + | RowLayout { | |
| 61 | + | Layout.fillWidth: true | |
| 62 | + | Text { | |
| 63 | + | Layout.fillWidth: true | |
| 64 | + | text: "~/.config/aurora/settings.json" | |
| 65 | + | elide: Text.ElideLeft | |
| 66 | + | color: Theme.dim | |
| 67 | + | font.family: Theme.font | |
| 68 | + | font.pixelSize: Theme.fontSize - 2 | |
| 69 | + | } | |
| 70 | + | IconBtn { icon: Theme.iRefresh; label: "reset shell settings"; onClicked: Settings.reset() } | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | } | |
| @@ -13,7 +13,8 @@ Singleton { | |||
| 13 | 13 | readonly property var defaults: ({ | |
| 14 | 14 | fontSize: 12, wsFontSize: 11, barHeight: 22, barAlpha: 1.0, panelAlpha: 0.88, accentIndex: 14, | |
| 15 | 15 | osdTimeout: 1500, desktopClock: true, desktopViz: true, vizBars: 64, | |
| 16 | − | dock: true, dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"], wallFolder: "dusky-dark" | |
| 16 | + | dock: true, dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"], wallFolder: "dusky-dark", | |
| 17 | + | notifSound: true, notifVolume: 0.7 | |
| 17 | 18 | }) | |
| 18 | 19 | ||
| 19 | 20 | property alias fontSize: a.fontSize | |
| @@ -29,6 +30,8 @@ Singleton { | |||
| 29 | 30 | property alias dock: a.dock | |
| 30 | 31 | property alias dockPinned: a.dockPinned | |
| 31 | 32 | property alias wallFolder: a.wallFolder | |
| 33 | + | property alias notifSound: a.notifSound | |
| 34 | + | property alias notifVolume: a.notifVolume | |
| 32 | 35 | ||
| 33 | 36 | function reset() { | |
| 34 | 37 | for (var k in defaults) a[k] = defaults[k] | |
| @@ -66,6 +69,8 @@ Singleton { | |||
| 66 | 69 | property bool dock: true | |
| 67 | 70 | property list<string> dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"] | |
| 68 | 71 | property string wallFolder: "dusky-dark" | |
| 72 | + | property bool notifSound: true | |
| 73 | + | property real notifVolume: 0.7 | |
| 69 | 74 | } | |
| 70 | 75 | } | |
| 71 | 76 | // first run: create the file once the directory exists | |
| @@ -0,0 +1,152 @@ | |||
| 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 | + | ] | |
| 31 | + | readonly property int index: { | |
| 32 | + | for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i | |
| 33 | + | return 0 | |
| 34 | + | } | |
| 35 | + | function cycle(d) { sectionRequested(sections[(index + d + sections.length) % sections.length].id) } | |
| 36 | + | ||
| 37 | + | Item { | |
| 38 | + | anchors.fill: parent | |
| 39 | + | focus: true | |
| 40 | + | Keys.onPressed: e => { | |
| 41 | + | var ctrl = e.modifiers & Qt.ControlModifier | |
| 42 | + | if (e.key === Qt.Key_Escape) { win.closeRequested(); e.accepted = true } | |
| 43 | + | else if (ctrl && (e.key === Qt.Key_Tab || e.key === Qt.Key_J)) { win.cycle(1); e.accepted = true } | |
| 44 | + | else if (ctrl && (e.key === Qt.Key_Backtab || e.key === Qt.Key_K)) { win.cycle(-1); e.accepted = true } | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | RowLayout { | |
| 48 | + | anchors.fill: parent | |
| 49 | + | spacing: 0 | |
| 50 | + | ||
| 51 | + | // sidebar | |
| 52 | + | Rectangle { | |
| 53 | + | Layout.fillHeight: true | |
| 54 | + | Layout.preferredWidth: 200 | |
| 55 | + | color: Theme.surface | |
| 56 | + | ColumnLayout { | |
| 57 | + | anchors.fill: parent | |
| 58 | + | anchors.margins: 10 | |
| 59 | + | spacing: 2 | |
| 60 | + | Text { | |
| 61 | + | Layout.bottomMargin: 8 | |
| 62 | + | text: Theme.iCog + " aurora settings" | |
| 63 | + | color: Theme.accent | |
| 64 | + | font.family: Theme.font | |
| 65 | + | font.pixelSize: Theme.fontSize + 1 | |
| 66 | + | font.bold: true | |
| 67 | + | } | |
| 68 | + | Repeater { | |
| 69 | + | model: win.sections | |
| 70 | + | delegate: ColumnLayout { | |
| 71 | + | id: entry | |
| 72 | + | required property var modelData | |
| 73 | + | required property int index | |
| 74 | + | readonly property bool current: win.section === modelData.id | |
| 75 | + | readonly property bool firstOfGroup: index === 0 || win.sections[index - 1].group !== modelData.group | |
| 76 | + | Layout.fillWidth: true | |
| 77 | + | spacing: 2 | |
| 78 | + | Text { | |
| 79 | + | visible: entry.firstOfGroup | |
| 80 | + | Layout.topMargin: entry.index === 0 ? 0 : 8 | |
| 81 | + | text: entry.modelData.group.toUpperCase() | |
| 82 | + | color: Theme.dim | |
| 83 | + | font.family: Theme.font | |
| 84 | + | font.pixelSize: Theme.fontSize - 3 | |
| 85 | + | font.letterSpacing: 1.5 | |
| 86 | + | } | |
| 87 | + | Rectangle { | |
| 88 | + | Layout.fillWidth: true | |
| 89 | + | implicitHeight: 30 | |
| 90 | + | color: entry.current ? Theme.accent : (em.containsMouse ? Theme.surfaceHi : "transparent") | |
| 91 | + | Text { | |
| 92 | + | anchors.verticalCenter: parent.verticalCenter | |
| 93 | + | x: 10 | |
| 94 | + | text: entry.modelData.icon + " " + entry.modelData.name | |
| 95 | + | color: entry.current ? Theme.bg : Theme.fg | |
| 96 | + | font.family: Theme.font | |
| 97 | + | font.pixelSize: Theme.fontSize | |
| 98 | + | font.bold: entry.current | |
| 99 | + | } | |
| 100 | + | MouseArea { | |
| 101 | + | id: em | |
| 102 | + | anchors.fill: parent | |
| 103 | + | hoverEnabled: true | |
| 104 | + | cursorShape: Qt.PointingHandCursor | |
| 105 | + | onClicked: win.sectionRequested(entry.modelData.id) | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | Item { Layout.fillHeight: true } | |
| 111 | + | } | |
| 112 | + | } | |
| 113 | + | Rectangle { Layout.fillHeight: true; Layout.preferredWidth: 1; color: Theme.line } | |
| 114 | + | ||
| 115 | + | // content | |
| 116 | + | ColumnLayout { | |
| 117 | + | Layout.fillWidth: true | |
| 118 | + | Layout.fillHeight: true | |
| 119 | + | Layout.margins: 16 | |
| 120 | + | spacing: 12 | |
| 121 | + | Text { | |
| 122 | + | text: win.sections[win.index].name.toUpperCase() | |
| 123 | + | color: Theme.fg | |
| 124 | + | font.family: Theme.font | |
| 125 | + | font.pixelSize: Theme.fontSize + 4 | |
| 126 | + | font.bold: true | |
| 127 | + | font.letterSpacing: 2 | |
| 128 | + | } | |
| 129 | + | Loader { | |
| 130 | + | Layout.fillWidth: true | |
| 131 | + | Layout.fillHeight: true | |
| 132 | + | active: win.opened | |
| 133 | + | sourceComponent: { | |
| 134 | + | switch (win.section) { | |
| 135 | + | case "bluetooth": return btC | |
| 136 | + | case "sound": return soundC | |
| 137 | + | case "appearance": return appearanceC | |
| 138 | + | case "shell": return shellC | |
| 139 | + | default: return netC | |
| 140 | + | } | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | } | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | Component { id: netC; NetPage { active: true } } | |
| 148 | + | Component { id: btC; BtPage { active: true } } | |
| 149 | + | Component { id: soundC; AudioPage {} } | |
| 150 | + | Component { id: appearanceC; SetAppearance { onOverlayRequested: name => win.overlayRequested(name) } } | |
| 151 | + | Component { id: shellC; SetShell {} } | |
| 152 | + | } | |
| @@ -1,134 +0,0 @@ | |||
| 1 | − | import QtQuick | |
| 2 | − | import QtQuick.Layouts | |
| 3 | − | ||
| 4 | − | // Theme / shell knobs. Everything writes straight to Settings (persisted, applied live). | |
| 5 | − | ColumnLayout { | |
| 6 | − | id: page | |
| 7 | − | spacing: 10 | |
| 8 | − | ||
| 9 | − | // label + slider + value, for an integer or real setting | |
| 10 | − | component Knob: RowLayout { | |
| 11 | − | id: k | |
| 12 | − | property string label: "" | |
| 13 | − | property real value: 0 | |
| 14 | − | property real from: 0 | |
| 15 | − | property real to: 1 | |
| 16 | − | property int decimals: 0 | |
| 17 | − | signal moved(real v) | |
| 18 | − | Layout.fillWidth: true | |
| 19 | − | spacing: 8 | |
| 20 | − | Text { | |
| 21 | − | Layout.preferredWidth: 96 | |
| 22 | − | text: k.label | |
| 23 | − | color: Theme.fg | |
| 24 | − | font.family: Theme.font | |
| 25 | − | font.pixelSize: Theme.fontSize | |
| 26 | − | } | |
| 27 | − | BarSlider { | |
| 28 | − | Layout.fillWidth: true | |
| 29 | − | from: k.from | |
| 30 | − | to: k.to | |
| 31 | − | step: 1 / Math.max(1, (k.to - k.from) * (k.decimals ? 100 : 1)) | |
| 32 | − | value: k.value | |
| 33 | − | onMoved: v => k.moved(k.decimals ? Math.round(v * 100) / 100 : Math.round(v)) | |
| 34 | − | } | |
| 35 | − | Text { | |
| 36 | − | Layout.preferredWidth: 40 | |
| 37 | − | horizontalAlignment: Text.AlignRight | |
| 38 | − | text: k.decimals ? k.value.toFixed(2) : String(k.value) | |
| 39 | − | color: Theme.fg | |
| 40 | − | font.family: Theme.font | |
| 41 | − | font.pixelSize: Theme.fontSize | |
| 42 | − | } | |
| 43 | − | } | |
| 44 | − | ||
| 45 | − | SectionLabel { text: "appearance" } | |
| 46 | − | ||
| 47 | − | Knob { label: "font size"; from: 9; to: 18; value: Settings.fontSize; onMoved: v => Settings.fontSize = v } | |
| 48 | − | Knob { label: "ws icons"; from: 8; to: 18; value: Settings.wsFontSize; onMoved: v => Settings.wsFontSize = v } | |
| 49 | − | Knob { label: "bar height"; from: 18; to: 36; value: Settings.barHeight; onMoved: v => Settings.barHeight = v } | |
| 50 | − | Knob { label: "bar alpha"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v } | |
| 51 | − | Knob { label: "panel alpha"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v } | |
| 52 | − | ||
| 53 | − | SectionLabel { text: "accent" } | |
| 54 | − | ||
| 55 | − | GridLayout { | |
| 56 | − | Layout.fillWidth: true | |
| 57 | − | columns: 9 | |
| 58 | − | columnSpacing: 4 | |
| 59 | − | rowSpacing: 4 | |
| 60 | − | Repeater { | |
| 61 | − | model: 17 // "auto" + color0..color15 | |
| 62 | − | delegate: Rectangle { | |
| 63 | − | id: sw | |
| 64 | − | required property int index | |
| 65 | − | readonly property int ci: index - 1 // -1 = auto | |
| 66 | − | readonly property bool current: Settings.accentIndex === ci | |
| 67 | − | Layout.fillWidth: true | |
| 68 | − | Layout.preferredHeight: 26 | |
| 69 | − | color: ci < 0 ? Theme.surface : Theme.pal[ci] | |
| 70 | − | border.width: current ? 2 : 1 | |
| 71 | − | border.color: current ? Theme.fg : Theme.line | |
| 72 | − | Text { | |
| 73 | − | anchors.centerIn: parent | |
| 74 | − | visible: sw.ci < 0 || sw.current | |
| 75 | − | text: sw.ci < 0 ? "auto" : Theme.iCheck | |
| 76 | − | color: sw.ci < 0 ? Theme.fg : Theme.bg | |
| 77 | − | font.family: Theme.font | |
| 78 | − | font.pixelSize: Theme.fontSize - 2 | |
| 79 | − | font.bold: true | |
| 80 | − | } | |
| 81 | − | MouseArea { | |
| 82 | − | anchors.fill: parent | |
| 83 | − | cursorShape: Qt.PointingHandCursor | |
| 84 | − | onClicked: Settings.accentIndex = sw.ci | |
| 85 | − | } | |
| 86 | − | } | |
| 87 | − | } | |
| 88 | − | } | |
| 89 | − | ||
| 90 | − | SectionLabel { text: "desktop" } | |
| 91 | − | ||
| 92 | − | RowLayout { | |
| 93 | − | Layout.fillWidth: true | |
| 94 | − | spacing: 6 | |
| 95 | − | IconBtn { | |
| 96 | − | Layout.fillWidth: true | |
| 97 | − | icon: Theme.iClock | |
| 98 | − | label: "clock" | |
| 99 | − | active: Settings.desktopClock | |
| 100 | − | onClicked: Settings.desktopClock = !Settings.desktopClock | |
| 101 | − | } | |
| 102 | − | IconBtn { | |
| 103 | − | Layout.fillWidth: true | |
| 104 | − | icon: Theme.iMusic | |
| 105 | − | label: "visualizer" | |
| 106 | − | active: Settings.desktopViz | |
| 107 | − | onClicked: Settings.desktopViz = !Settings.desktopViz | |
| 108 | − | } | |
| 109 | − | IconBtn { | |
| 110 | − | Layout.fillWidth: true | |
| 111 | − | icon: Theme.iDock | |
| 112 | − | label: "dock" | |
| 113 | − | active: Settings.dock | |
| 114 | − | onClicked: Settings.dock = !Settings.dock | |
| 115 | − | } | |
| 116 | − | } | |
| 117 | − | Knob { label: "viz bars"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v } | |
| 118 | − | Knob { label: "osd ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 } | |
| 119 | − | ||
| 120 | − | Item { Layout.fillHeight: true } | |
| 121 | − | ||
| 122 | − | RowLayout { | |
| 123 | − | Layout.fillWidth: true | |
| 124 | − | Text { | |
| 125 | − | Layout.fillWidth: true | |
| 126 | − | text: "~/.config/aurora/settings.json" | |
| 127 | − | elide: Text.ElideLeft | |
| 128 | − | color: Theme.dim | |
| 129 | − | font.family: Theme.font | |
| 130 | − | font.pixelSize: Theme.fontSize - 2 | |
| 131 | − | } | |
| 132 | − | IconBtn { icon: Theme.iRefresh; label: "reset"; onClicked: Settings.reset() } | |
| 133 | − | } | |
| 134 | − | } | |
| @@ -0,0 +1,65 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // label (+ optional hint) and an on/off box; the whole row is clickable (settings app) | |
| 5 | + | Rectangle { | |
| 6 | + | id: s | |
| 7 | + | property string label: "" | |
| 8 | + | property string hint: "" | |
| 9 | + | property bool checked: false | |
| 10 | + | signal toggled(bool on) | |
| 11 | + | ||
| 12 | + | Layout.fillWidth: true | |
| 13 | + | implicitHeight: Math.max(30, txt.implicitHeight + 10) | |
| 14 | + | color: m.containsMouse ? Theme.surface : "transparent" | |
| 15 | + | ||
| 16 | + | ColumnLayout { | |
| 17 | + | id: txt | |
| 18 | + | anchors.left: parent.left | |
| 19 | + | anchors.right: box.left | |
| 20 | + | anchors.rightMargin: 10 | |
| 21 | + | anchors.verticalCenter: parent.verticalCenter | |
| 22 | + | spacing: 0 | |
| 23 | + | Text { | |
| 24 | + | Layout.fillWidth: true | |
| 25 | + | text: s.label | |
| 26 | + | color: Theme.fg | |
| 27 | + | elide: Text.ElideRight | |
| 28 | + | font.family: Theme.font | |
| 29 | + | font.pixelSize: Theme.fontSize | |
| 30 | + | } | |
| 31 | + | Text { | |
| 32 | + | Layout.fillWidth: true | |
| 33 | + | visible: s.hint !== "" | |
| 34 | + | text: s.hint | |
| 35 | + | color: Theme.dim | |
| 36 | + | wrapMode: Text.WordWrap | |
| 37 | + | font.family: Theme.font | |
| 38 | + | font.pixelSize: Theme.fontSize - 2 | |
| 39 | + | } | |
| 40 | + | } | |
| 41 | + | Rectangle { // the switch: square track, knob left (off) / right (on) | |
| 42 | + | id: box | |
| 43 | + | anchors.right: parent.right | |
| 44 | + | anchors.verticalCenter: parent.verticalCenter | |
| 45 | + | width: 36 | |
| 46 | + | height: 18 | |
| 47 | + | color: s.checked ? Theme.accent : Theme.surfaceHi | |
| 48 | + | border.width: 1 | |
| 49 | + | border.color: s.checked ? Theme.accent : Theme.line | |
| 50 | + | Rectangle { | |
| 51 | + | width: 12 | |
| 52 | + | height: 12 | |
| 53 | + | anchors.verticalCenter: parent.verticalCenter | |
| 54 | + | x: s.checked ? parent.width - width - 3 : 3 | |
| 55 | + | color: s.checked ? Theme.bg : Theme.dim | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | MouseArea { | |
| 59 | + | id: m | |
| 60 | + | anchors.fill: parent | |
| 61 | + | hoverEnabled: true | |
| 62 | + | cursorShape: Qt.PointingHandCursor | |
| 63 | + | onClicked: s.toggled(!s.checked) | |
| 64 | + | } | |
| 65 | + | } | |
| @@ -7,7 +7,7 @@ import Quickshell.Io | |||
| 7 | 7 | Singleton { | |
| 8 | 8 | id: root | |
| 9 | 9 | ||
| 10 | − | // ---- knobs (persisted in ~/.config/aurora/settings.json, edited in the Settings tab) ---- | |
| 10 | + | // ---- knobs (persisted in ~/.config/aurora/settings.json, edited in the settings app) ---- | |
| 11 | 11 | property string font: "JetBrainsMono Nerd Font" // any Nerd Font mono | |
| 12 | 12 | readonly property int fontSize: Settings.fontSize | |
| 13 | 13 | readonly property int wsFontSize: Settings.wsFontSize // workspace icons only; everything else uses fontSize | |
| @@ -164,6 +164,7 @@ Singleton { | |||
| 164 | 164 | readonly property string iConsole: cp(0xF018D) | |
| 165 | 165 | readonly property string iShell: cp(0xF07B7) | |
| 166 | 166 | readonly property string iCalc: cp(0xF00EC) | |
| 167 | + | readonly property string iPalette: cp(0xF03D8) | |
| 167 | 168 | ||
| 168 | 169 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 169 | 170 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |
| @@ -19,7 +19,10 @@ ShellRoot { | |||
| 19 | 19 | property string overlay: "" | |
| 20 | 20 | property bool barShown: true // `bar` IPC (SUPER+B); not persisted, a restart shows it again | |
| 21 | 21 | property var overlayScreen: focusedScreen() | |
| 22 | − | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif", "settings"] | |
| 22 | + | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif"] | |
| 23 | + | // settings app (a normal window): open state + shown section | |
| 24 | + | property bool settingsOpen: false | |
| 25 | + | property string settingsSection: "network" | |
| 23 | 26 | ||
| 24 | 27 | // screen of the monitor that currently has focus (falls back to the first screen) | |
| 25 | 28 | function focusedScreen() { | |
| @@ -51,7 +54,14 @@ ShellRoot { | |||
| 51 | 54 | overlayScreen = scr ? scr : focusedScreen() | |
| 52 | 55 | overlay = name | |
| 53 | 56 | } | |
| 57 | + | // section "" = toggle the window on its last section; a name opens (or switches) to it | |
| 58 | + | function settings(section) { | |
| 59 | + | if (section === "" || section === undefined) { settingsOpen = !settingsOpen; return } | |
| 60 | + | settingsSection = section | |
| 61 | + | settingsOpen = true | |
| 62 | + | } | |
| 54 | 63 | function openTab(name) { | |
| 64 | + | if (name === "settings") { settings("appearance"); return } // old panel tab lives in the app now | |
| 55 | 65 | var i = tabNames.indexOf(name) | |
| 56 | 66 | if (i >= 0) tab = i | |
| 57 | 67 | panelScreen = focusedScreen() | |
| @@ -99,6 +109,7 @@ ShellRoot { | |||
| 99 | 109 | onTabRequested: i => root.tab = i | |
| 100 | 110 | onCloseRequested: root.close() | |
| 101 | 111 | onOverlayRequested: name => { root.close(); root.toggleOverlay(name) } | |
| 112 | + | onSettingsRequested: { root.close(); root.settingsOpen = true } | |
| 102 | 113 | } | |
| 103 | 114 | ||
| 104 | 115 | Overview { | |
| @@ -145,6 +156,14 @@ ShellRoot { | |||
| 145 | 156 | } | |
| 146 | 157 | } | |
| 147 | 158 | ||
| 159 | + | SettingsApp { | |
| 160 | + | opened: root.settingsOpen | |
| 161 | + | section: root.settingsSection | |
| 162 | + | onCloseRequested: root.settingsOpen = false | |
| 163 | + | onSectionRequested: name => root.settingsSection = name | |
| 164 | + | onOverlayRequested: name => root.toggleOverlay(name) | |
| 165 | + | } | |
| 166 | + | ||
| 148 | 167 | NotifPopups { screen: root.focusedScr; suppressed: root.opened } | |
| 149 | 168 | ||
| 150 | 169 | Osd { id: osd; screen: root.focusedScr; suppressed: root.opened } | |
| @@ -180,6 +199,7 @@ ShellRoot { | |||
| 180 | 199 | function wallrandom(): void { Wall.random() } | |
| 181 | 200 | function wallset(path: string): void { Wall.apply(path) } // absolute path (setwall resolves it) | |
| 182 | 201 | function rec(mode: string): void { root.close(); Rec.toggle(mode === "region" ? "region" : "screen") } // screen (default) | region | |
| 202 | + | function settings(section: string): void { root.settings(section) } // "" = toggle | |
| 183 | 203 | function caffeine(): void { Toggles.caffeine = !Toggles.caffeine } | |
| 184 | 204 | function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down | |
| 185 | 205 | } | |