auroraed7f69d
aurora: settings app in cards with an icon nav
+1350 −1017
17 files changed
- M .config/quickshell/aurora/AudioPage.qml +101 −73
- M .config/quickshell/aurora/BatteryInfo.qml +14 −8
- M .config/quickshell/aurora/BtPage.qml +93 −77
- M .config/quickshell/aurora/GpuSwitch.qml +9 −5
- A .config/quickshell/aurora/Group.qml +41 −0
- M .config/quickshell/aurora/NetPage.qml +152 −135
- M .config/quickshell/aurora/SetAbout.qml +96 −68
- M .config/quickshell/aurora/SetAppearance.qml +134 −105
- M .config/quickshell/aurora/SetDisplay.qml +68 −49
- M .config/quickshell/aurora/SetIndex.qml +17 −17
- M .config/quickshell/aurora/SetInput.qml +51 −36
- M .config/quickshell/aurora/SetKeys.qml +53 −31
- M .config/quickshell/aurora/SetPower.qml +97 −81
- M .config/quickshell/aurora/SetShell.qml +82 −57
- M .config/quickshell/aurora/SetWindows.qml +72 −60
- M .config/quickshell/aurora/SettingsApp.qml +269 −215
- M .config/quickshell/aurora/Theme.qml +1 −0
| @@ -5,6 +5,7 @@ import Quickshell.Services.Pipewire | |||
| 5 | 5 | // Control panel > sound (also the settings app's sound section): output / input levels, devices, per-app volume. | |
| 6 | 6 | Flickable { | |
| 7 | 7 | id: flick | |
| 8 | + | property bool cards: false // the settings app: groups on cards | |
| 8 | 9 | clip: true | |
| 9 | 10 | contentWidth: width | |
| 10 | 11 | contentHeight: col.implicitHeight | |
| @@ -42,86 +43,113 @@ Flickable { | |||
| 42 | 43 | ColumnLayout { | |
| 43 | 44 | id: col | |
| 44 | 45 | width: flick.width | |
| 45 | − | spacing: 6 | |
| 46 | + | spacing: flick.cards ? 22 : 6 | |
| 46 | 47 | ||
| 47 | − | SectionLabel { text: "levels"; keywords: "volume mute microphone mic" } | |
| 48 | − | SliderRow { | |
| 49 | − | Layout.fillWidth: true | |
| 50 | − | Layout.topMargin: 4 | |
| 51 | − | icon: Theme.volIcon(flick.out ? flick.out.volume : 0, flick.out ? flick.out.muted : true) | |
| 52 | − | value: flick.out ? flick.out.volume : 0 | |
| 53 | − | muted: flick.out ? flick.out.muted : false | |
| 54 | − | onMoved: v => { if (flick.out) flick.out.volume = v } | |
| 55 | − | onIconClicked: if (flick.out) flick.out.muted = !flick.out.muted | |
| 56 | − | } | |
| 57 | − | SliderRow { | |
| 58 | − | Layout.fillWidth: true | |
| 59 | − | Layout.topMargin: 6 | |
| 60 | − | icon: flick.inp && !flick.inp.muted ? Theme.iMic : Theme.iMicOff | |
| 61 | − | value: flick.inp ? flick.inp.volume : 0 | |
| 62 | − | muted: flick.inp ? flick.inp.muted : false | |
| 63 | − | onMoved: v => { if (flick.inp) flick.inp.volume = v } | |
| 64 | − | onIconClicked: if (flick.inp) flick.inp.muted = !flick.inp.muted | |
| 65 | − | } | |
| 66 | − | ||
| 67 | − | SectionLabel { text: "output"; keywords: "speakers headphones hdmi default device sink"; count: String(flick.sinks.length); Layout.topMargin: 14 } | |
| 68 | − | Repeater { | |
| 69 | − | model: flick.sinks | |
| 70 | − | delegate: ListRow { | |
| 71 | − | required property var modelData | |
| 72 | − | icon: flick.sinkIcon(modelData) | |
| 73 | − | title: flick.label(modelData) | |
| 74 | − | subtitle: current ? "default" : "" | |
| 75 | − | current: Pipewire.defaultAudioSink === modelData | |
| 76 | − | trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "" | |
| 77 | − | onClicked: Pipewire.preferredDefaultAudioSink = modelData | |
| 48 | + | Group { | |
| 49 | + | title: "levels" | |
| 50 | + | keywords: "volume mute microphone mic" | |
| 51 | + | flat: !flick.cards | |
| 52 | + | spacing: 6 | |
| 53 | + | gap: 6 | |
| 54 | + | SliderRow { | |
| 55 | + | Layout.fillWidth: true | |
| 56 | + | Layout.topMargin: 4 | |
| 57 | + | icon: Theme.volIcon(flick.out ? flick.out.volume : 0, flick.out ? flick.out.muted : true) | |
| 58 | + | value: flick.out ? flick.out.volume : 0 | |
| 59 | + | muted: flick.out ? flick.out.muted : false | |
| 60 | + | onMoved: v => { if (flick.out) flick.out.volume = v } | |
| 61 | + | onIconClicked: if (flick.out) flick.out.muted = !flick.out.muted | |
| 78 | 62 | } | |
| 79 | − | } | |
| 80 | − | ||
| 81 | − | SectionLabel { text: "input"; keywords: "microphone mic default device source"; count: String(flick.sources.length); Layout.topMargin: 14 } | |
| 82 | − | Repeater { | |
| 83 | − | model: flick.sources | |
| 84 | − | delegate: ListRow { | |
| 85 | − | required property var modelData | |
| 86 | − | icon: Theme.iMic | |
| 87 | − | title: flick.label(modelData) | |
| 88 | − | subtitle: current ? "default" : "" | |
| 89 | − | current: Pipewire.defaultAudioSource === modelData | |
| 90 | − | trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "" | |
| 91 | − | onClicked: Pipewire.preferredDefaultAudioSource = modelData | |
| 63 | + | SliderRow { | |
| 64 | + | Layout.fillWidth: true | |
| 65 | + | Layout.topMargin: 6 | |
| 66 | + | icon: flick.inp && !flick.inp.muted ? Theme.iMic : Theme.iMicOff | |
| 67 | + | value: flick.inp ? flick.inp.volume : 0 | |
| 68 | + | muted: flick.inp ? flick.inp.muted : false | |
| 69 | + | onMoved: v => { if (flick.inp) flick.inp.volume = v } | |
| 70 | + | onIconClicked: if (flick.inp) flick.inp.muted = !flick.inp.muted | |
| 92 | 71 | } | |
| 93 | 72 | } | |
| 94 | − | ||
| 95 | − | SectionLabel { text: "apps"; keywords: "mixer per-app app volume streams"; count: String(flick.streams.length); Layout.topMargin: 14 } | |
| 96 | − | Lb { | |
| 97 | − | visible: flick.streams.length === 0 | |
| 98 | − | Layout.leftMargin: 6 | |
| 99 | − | text: "no app is playing" | |
| 100 | − | color: Theme.mute | |
| 73 | + | Group { | |
| 74 | + | title: "output" | |
| 75 | + | keywords: "speakers headphones hdmi default device sink" | |
| 76 | + | count: String(flick.sinks.length) | |
| 77 | + | flat: !flick.cards | |
| 78 | + | spacing: 6 | |
| 79 | + | gap: 6 | |
| 80 | + | Layout.topMargin: flick.cards ? 0 : 14 | |
| 81 | + | Repeater { | |
| 82 | + | model: flick.sinks | |
| 83 | + | delegate: ListRow { | |
| 84 | + | required property var modelData | |
| 85 | + | icon: flick.sinkIcon(modelData) | |
| 86 | + | title: flick.label(modelData) | |
| 87 | + | subtitle: current ? "default" : "" | |
| 88 | + | current: Pipewire.defaultAudioSink === modelData | |
| 89 | + | trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "" | |
| 90 | + | onClicked: Pipewire.preferredDefaultAudioSink = modelData | |
| 91 | + | } | |
| 92 | + | } | |
| 101 | 93 | } | |
| 102 | − | Repeater { | |
| 103 | − | model: flick.streams | |
| 104 | − | delegate: ColumnLayout { | |
| 105 | − | required property var modelData | |
| 106 | − | Layout.fillWidth: true | |
| 107 | − | Layout.topMargin: 4 | |
| 108 | − | spacing: 6 | |
| 109 | − | Lb { | |
| 110 | − | Layout.leftMargin: 6 | |
| 111 | − | Layout.fillWidth: true | |
| 112 | − | elide: Text.ElideRight | |
| 113 | − | px: 10 | |
| 114 | − | text: (modelData.properties && modelData.properties["application.name"]) | |
| 115 | − | ? modelData.properties["application.name"] : flick.label(modelData) | |
| 94 | + | Group { | |
| 95 | + | title: "input" | |
| 96 | + | keywords: "microphone mic default device source" | |
| 97 | + | count: String(flick.sources.length) | |
| 98 | + | flat: !flick.cards | |
| 99 | + | spacing: 6 | |
| 100 | + | gap: 6 | |
| 101 | + | Layout.topMargin: flick.cards ? 0 : 14 | |
| 102 | + | Repeater { | |
| 103 | + | model: flick.sources | |
| 104 | + | delegate: ListRow { | |
| 105 | + | required property var modelData | |
| 106 | + | icon: Theme.iMic | |
| 107 | + | title: flick.label(modelData) | |
| 108 | + | subtitle: current ? "default" : "" | |
| 109 | + | current: Pipewire.defaultAudioSource === modelData | |
| 110 | + | trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "" | |
| 111 | + | onClicked: Pipewire.preferredDefaultAudioSource = modelData | |
| 116 | 112 | } | |
| 117 | − | SliderRow { | |
| 113 | + | } | |
| 114 | + | } | |
| 115 | + | Group { | |
| 116 | + | title: "apps" | |
| 117 | + | keywords: "mixer per-app app volume streams" | |
| 118 | + | count: String(flick.streams.length) | |
| 119 | + | flat: !flick.cards | |
| 120 | + | spacing: 6 | |
| 121 | + | gap: 6 | |
| 122 | + | Layout.topMargin: flick.cards ? 0 : 14 | |
| 123 | + | Lb { | |
| 124 | + | visible: flick.streams.length === 0 | |
| 125 | + | Layout.leftMargin: 6 | |
| 126 | + | text: "no app is playing" | |
| 127 | + | color: Theme.mute | |
| 128 | + | } | |
| 129 | + | Repeater { | |
| 130 | + | model: flick.streams | |
| 131 | + | delegate: ColumnLayout { | |
| 132 | + | required property var modelData | |
| 118 | 133 | Layout.fillWidth: true | |
| 119 | − | implicitHeight: 44 | |
| 120 | − | icon: Theme.volIcon(modelData.audio.volume, modelData.audio.muted) | |
| 121 | − | value: modelData.audio.volume | |
| 122 | − | muted: modelData.audio.muted | |
| 123 | − | onMoved: v => modelData.audio.volume = v | |
| 124 | − | onIconClicked: modelData.audio.muted = !modelData.audio.muted | |
| 134 | + | Layout.topMargin: 4 | |
| 135 | + | spacing: 6 | |
| 136 | + | Lb { | |
| 137 | + | Layout.leftMargin: 6 | |
| 138 | + | Layout.fillWidth: true | |
| 139 | + | elide: Text.ElideRight | |
| 140 | + | px: 10 | |
| 141 | + | text: (modelData.properties && modelData.properties["application.name"]) | |
| 142 | + | ? modelData.properties["application.name"] : flick.label(modelData) | |
| 143 | + | } | |
| 144 | + | SliderRow { | |
| 145 | + | Layout.fillWidth: true | |
| 146 | + | implicitHeight: 44 | |
| 147 | + | icon: Theme.volIcon(modelData.audio.volume, modelData.audio.muted) | |
| 148 | + | value: modelData.audio.volume | |
| 149 | + | muted: modelData.audio.muted | |
| 150 | + | onMoved: v => modelData.audio.volume = v | |
| 151 | + | onIconClicked: modelData.audio.muted = !modelData.audio.muted | |
| 152 | + | } | |
| 125 | 153 | } | |
| 126 | 154 | } | |
| 127 | 155 | } | |
| @@ -3,8 +3,10 @@ import QtQuick.Layouts | |||
| 3 | 3 | import Quickshell.Services.UPower | |
| 4 | 4 | ||
| 5 | 5 | // Battery block (SysPage, settings app > Power): charge in Doto, state, a segment bar (accent when low), details. | |
| 6 | − | ColumnLayout { | |
| 6 | + | // cards: on a card (the settings app); the panel shows it flat. | |
| 7 | + | Group { | |
| 7 | 8 | id: root | |
| 9 | + | property bool cards: false | |
| 8 | 10 | readonly property var bat: UPower.displayDevice | |
| 9 | 11 | readonly property bool hasBat: bat !== null && bat.isPresent | |
| 10 | 12 | readonly property real batPct: hasBat ? (bat.percentage > 1 ? bat.percentage / 100 : bat.percentage) : 0 | |
| @@ -22,12 +24,15 @@ ColumnLayout { | |||
| 22 | 24 | } | |
| 23 | 25 | } | |
| 24 | 26 | visible: hasBat | |
| 25 | − | Layout.fillWidth: true | |
| 26 | − | spacing: 12 | |
| 27 | + | title: "battery" | |
| 28 | + | keywords: "charge level percentage health time left" | |
| 29 | + | flat: !cards | |
| 30 | + | spacing: cards ? 10 : 12 | |
| 31 | + | gap: 12 | |
| 27 | 32 | ||
| 28 | − | SectionLabel { text: "battery"; keywords: "charge level percentage health time left" } | |
| 29 | 33 | RowLayout { | |
| 30 | − | Layout.leftMargin: 6 | |
| 34 | + | Layout.leftMargin: root.cards ? 18 : 6 | |
| 35 | + | Layout.topMargin: root.cards ? 12 : 0 | |
| 31 | 36 | spacing: 14 | |
| 32 | 37 | Num { | |
| 33 | 38 | text: Math.round(root.batPct * 100) | |
| @@ -43,15 +48,16 @@ ColumnLayout { | |||
| 43 | 48 | Layout.fillWidth: true | |
| 44 | 49 | Layout.preferredWidth: 100 | |
| 45 | 50 | Layout.preferredHeight: 6 | |
| 46 | − | Layout.leftMargin: 6 | |
| 47 | − | Layout.rightMargin: 6 | |
| 51 | + | Layout.leftMargin: root.cards ? 18 : 6 | |
| 52 | + | Layout.rightMargin: root.cards ? 18 : 6 | |
| 48 | 53 | n: 24 | |
| 49 | 54 | f: root.batPct | |
| 50 | 55 | on: root.low ? Theme.err : Theme.fill | |
| 51 | 56 | } | |
| 52 | 57 | Lb { | |
| 53 | 58 | Layout.fillWidth: true | |
| 54 | − | Layout.leftMargin: 6 | |
| 59 | + | Layout.leftMargin: root.cards ? 18 : 6 | |
| 60 | + | Layout.bottomMargin: root.cards ? 12 : 0 | |
| 55 | 61 | wrapMode: Text.Wrap | |
| 56 | 62 | lineHeight: 1.5 | |
| 57 | 63 | px: 10 | |
| @@ -7,7 +7,8 @@ import Quickshell.Bluetooth | |||
| 7 | 7 | ColumnLayout { | |
| 8 | 8 | id: page | |
| 9 | 9 | property bool active: false | |
| 10 | − | spacing: 10 | |
| 10 | + | property bool cards: false // the settings app: groups on cards | |
| 11 | + | spacing: cards ? 22 : 10 | |
| 11 | 12 | ||
| 12 | 13 | readonly property var adapter: Bluetooth.defaultAdapter | |
| 13 | 14 | ||
| @@ -53,96 +54,111 @@ ColumnLayout { | |||
| 53 | 54 | } | |
| 54 | 55 | readonly property var devices: list() | |
| 55 | 56 | ||
| 56 | − | RowLayout { | |
| 57 | − | Layout.fillWidth: true | |
| 58 | − | Layout.leftMargin: 6 | |
| 59 | − | spacing: 8 | |
| 60 | − | Lb { | |
| 57 | + | // the radio: a card of its own in the settings app | |
| 58 | + | Group { | |
| 59 | + | flat: !page.cards | |
| 60 | + | RowLayout { | |
| 61 | 61 | Layout.fillWidth: true | |
| 62 | − | elide: Text.ElideRight | |
| 63 | − | text: !page.adapter ? "no bluetooth adapter" | |
| 64 | − | : (!page.adapter.enabled ? "bluetooth · off" | |
| 65 | − | : (page.adapter.discovering ? "bluetooth · searching" : "bluetooth · " + (page.adapter.name || "on"))) | |
| 66 | − | color: page.adapter && page.adapter.enabled ? Theme.fg : Theme.grey | |
| 67 | − | } | |
| 68 | − | IconBtn { | |
| 69 | − | visible: page.adapter !== null | |
| 70 | − | icon: Theme.iSearch | |
| 71 | − | active: page.adapter ? page.adapter.discovering : false | |
| 72 | − | onClicked: if (page.adapter && page.adapter.enabled) page.adapter.discovering = !page.adapter.discovering | |
| 73 | − | } | |
| 74 | − | IconBtn { | |
| 75 | − | visible: page.adapter !== null | |
| 76 | − | icon: page.adapter && page.adapter.enabled ? Theme.iBt : Theme.iBtOff | |
| 77 | − | active: page.adapter ? page.adapter.enabled : false | |
| 78 | − | onClicked: { | |
| 79 | − | if (!page.adapter) return | |
| 80 | − | page.adapter.enabled = !page.adapter.enabled | |
| 81 | − | if (page.adapter.enabled && page.active) page.adapter.discovering = true | |
| 62 | + | Layout.leftMargin: page.cards ? 18 : 6 | |
| 63 | + | Layout.preferredHeight: page.cards ? 56 : -1 | |
| 64 | + | spacing: 8 | |
| 65 | + | Lb { | |
| 66 | + | Layout.fillWidth: true | |
| 67 | + | elide: Text.ElideRight | |
| 68 | + | text: !page.adapter ? "no bluetooth adapter" | |
| 69 | + | : (!page.adapter.enabled ? "bluetooth · off" | |
| 70 | + | : (page.adapter.discovering ? "bluetooth · searching" : "bluetooth · " + (page.adapter.name || "on"))) | |
| 71 | + | color: page.adapter && page.adapter.enabled ? Theme.fg : Theme.grey | |
| 72 | + | } | |
| 73 | + | IconBtn { | |
| 74 | + | visible: page.adapter !== null | |
| 75 | + | icon: Theme.iSearch | |
| 76 | + | active: page.adapter ? page.adapter.discovering : false | |
| 77 | + | onClicked: if (page.adapter && page.adapter.enabled) page.adapter.discovering = !page.adapter.discovering | |
| 78 | + | } | |
| 79 | + | IconBtn { | |
| 80 | + | visible: page.adapter !== null | |
| 81 | + | icon: page.adapter && page.adapter.enabled ? Theme.iBt : Theme.iBtOff | |
| 82 | + | active: page.adapter ? page.adapter.enabled : false | |
| 83 | + | onClicked: { | |
| 84 | + | if (!page.adapter) return | |
| 85 | + | page.adapter.enabled = !page.adapter.enabled | |
| 86 | + | if (page.adapter.enabled && page.active) page.adapter.discovering = true | |
| 87 | + | } | |
| 82 | 88 | } | |
| 83 | 89 | } | |
| 84 | 90 | } | |
| 85 | 91 | ||
| 86 | − | SectionLabel { text: "devices"; keywords: "pair pairing connect forget headphones headset earbuds speaker controller"; count: page.adapter && page.adapter.enabled ? String(page.devices.length) : "" } | |
| 87 | − | ||
| 88 | − | Flickable { | |
| 89 | − | id: flick | |
| 90 | − | Layout.fillWidth: true | |
| 92 | + | Group { | |
| 93 | + | title: "devices" | |
| 94 | + | keywords: "pair pairing connect forget headphones headset earbuds speaker controller" | |
| 95 | + | count: page.adapter && page.adapter.enabled ? String(page.devices.length) : "" | |
| 96 | + | flat: !page.cards | |
| 97 | + | spacing: 10 | |
| 91 | 98 | Layout.fillHeight: true | |
| 92 | − | Layout.preferredHeight: col.implicitHeight | |
| 93 | − | clip: true | |
| 94 | − | contentWidth: width | |
| 95 | − | contentHeight: col.implicitHeight | |
| 96 | − | boundsBehavior: Flickable.StopAtBounds | |
| 99 | + | // on a card: only as tall as the list, it scrolls inside once the page is full | |
| 100 | + | Layout.maximumHeight: page.cards ? implicitHeight : Infinity | |
| 97 | 101 | ||
| 98 | − | ColumnLayout { | |
| 99 | − | id: col | |
| 100 | − | width: flick.width | |
| 101 | − | spacing: 2 | |
| 102 | + | Flickable { | |
| 103 | + | id: flick | |
| 104 | + | Layout.fillWidth: true | |
| 105 | + | Layout.fillHeight: true | |
| 106 | + | Layout.preferredHeight: col.implicitHeight | |
| 107 | + | clip: true | |
| 108 | + | contentWidth: width | |
| 109 | + | contentHeight: col.implicitHeight | |
| 110 | + | boundsBehavior: Flickable.StopAtBounds | |
| 102 | 111 | ||
| 103 | − | Repeater { | |
| 104 | − | model: page.adapter && page.adapter.enabled ? page.devices : [] | |
| 105 | − | delegate: ListRow { | |
| 106 | − | id: row | |
| 107 | − | required property var modelData | |
| 108 | − | readonly property var dev: modelData | |
| 109 | − | property bool pendingConnect: false | |
| 112 | + | ColumnLayout { | |
| 113 | + | id: col | |
| 114 | + | width: flick.width | |
| 115 | + | spacing: 2 | |
| 110 | 116 | ||
| 111 | − | function afterPair() { | |
| 112 | − | if (pendingConnect && page.paired(dev)) { pendingConnect = false; dev.connect() } | |
| 113 | − | } | |
| 117 | + | Repeater { | |
| 118 | + | model: page.adapter && page.adapter.enabled ? page.devices : [] | |
| 119 | + | delegate: ListRow { | |
| 120 | + | id: row | |
| 121 | + | required property var modelData | |
| 122 | + | readonly property var dev: modelData | |
| 123 | + | property bool pendingConnect: false | |
| 114 | 124 | ||
| 115 | − | icon: page.devIcon(dev) | |
| 116 | − | title: dev.name || dev.address | |
| 117 | − | current: dev.connected | |
| 118 | − | subtitle: { | |
| 119 | − | if (dev.state === BluetoothDeviceState.Connecting) return "connecting…" | |
| 120 | − | if (dev.pairing) return "pairing…" | |
| 121 | − | if (dev.connected) | |
| 122 | − | return "connected" + (dev.batteryAvailable ? " · " + Math.round(dev.battery * 100) + "%" : "") | |
| 123 | − | return page.paired(dev) ? "paired" : "available" | |
| 124 | − | } | |
| 125 | − | onClicked: { | |
| 126 | − | if (dev.connected) dev.disconnect() | |
| 127 | − | else if (page.paired(dev)) dev.connect() | |
| 128 | − | else { pendingConnect = true; dev.trusted = true; dev.pair() } | |
| 129 | − | } | |
| 125 | + | function afterPair() { | |
| 126 | + | if (pendingConnect && page.paired(dev)) { pendingConnect = false; dev.connect() } | |
| 127 | + | } | |
| 130 | 128 | ||
| 131 | − | // property name differs between Quickshell versions | |
| 132 | − | Connections { | |
| 133 | − | target: row.dev | |
| 134 | − | ignoreUnknownSignals: true | |
| 135 | − | function onBondedChanged() { row.afterPair() } | |
| 136 | − | function onPairedChanged() { row.afterPair() } | |
| 137 | − | } | |
| 138 | − | IconBtn { | |
| 139 | − | visible: page.paired(row.dev) | |
| 140 | − | icon: Theme.iTrash | |
| 141 | − | implicitHeight: 32 | |
| 142 | − | onClicked: row.dev.forget() | |
| 129 | + | icon: page.devIcon(dev) | |
| 130 | + | title: dev.name || dev.address | |
| 131 | + | current: dev.connected | |
| 132 | + | subtitle: { | |
| 133 | + | if (dev.state === BluetoothDeviceState.Connecting) return "connecting…" | |
| 134 | + | if (dev.pairing) return "pairing…" | |
| 135 | + | if (dev.connected) | |
| 136 | + | return "connected" + (dev.batteryAvailable ? " · " + Math.round(dev.battery * 100) + "%" : "") | |
| 137 | + | return page.paired(dev) ? "paired" : "available" | |
| 138 | + | } | |
| 139 | + | onClicked: { | |
| 140 | + | if (dev.connected) dev.disconnect() | |
| 141 | + | else if (page.paired(dev)) dev.connect() | |
| 142 | + | else { pendingConnect = true; dev.trusted = true; dev.pair() } | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | // property name differs between Quickshell versions | |
| 146 | + | Connections { | |
| 147 | + | target: row.dev | |
| 148 | + | ignoreUnknownSignals: true | |
| 149 | + | function onBondedChanged() { row.afterPair() } | |
| 150 | + | function onPairedChanged() { row.afterPair() } | |
| 151 | + | } | |
| 152 | + | IconBtn { | |
| 153 | + | visible: page.paired(row.dev) | |
| 154 | + | icon: Theme.iTrash | |
| 155 | + | implicitHeight: 32 | |
| 156 | + | onClicked: row.dev.forget() | |
| 157 | + | } | |
| 143 | 158 | } | |
| 144 | 159 | } | |
| 145 | 160 | } | |
| 146 | 161 | } | |
| 147 | 162 | } | |
| 163 | + | Item { visible: page.cards; Layout.fillHeight: true } | |
| 148 | 164 | } | |
| @@ -1,9 +1,10 @@ | |||
| 1 | 1 | import QtQuick | |
| 2 | 2 | import QtQuick.Layouts | |
| 3 | 3 | ||
| 4 | − | // envycontrol mode pills with the two-click arm (SysPage, settings app > Power) | |
| 5 | − | ColumnLayout { | |
| 4 | + | // envycontrol mode pills with the two-click arm (SysPage, settings app > Power). cards: on a card (the settings app). | |
| 5 | + | Group { | |
| 6 | 6 | id: root | |
| 7 | + | property bool cards: false | |
| 7 | 8 | // two-click arm before switching (needs a reboot to apply) | |
| 8 | 9 | property string armedGpu: "" | |
| 9 | 10 | Timer { id: disarm; interval: 3000; onTriggered: root.armedGpu = "" } | |
| @@ -14,9 +15,11 @@ ColumnLayout { | |||
| 14 | 15 | Gpu.set(m) | |
| 15 | 16 | } | |
| 16 | 17 | visible: Gpu.mode !== "" | |
| 17 | − | Layout.fillWidth: true | |
| 18 | + | title: "graphics · envycontrol" | |
| 19 | + | keywords: "gpu graphics mode nvidia hybrid integrated dgpu prime" | |
| 20 | + | flat: !cards | |
| 18 | 21 | spacing: 10 | |
| 19 | − | SectionLabel { text: "graphics · envycontrol"; keywords: "gpu graphics mode nvidia hybrid integrated dgpu prime" } | |
| 22 | + | gap: 10 | |
| 20 | 23 | RowLayout { | |
| 21 | 24 | Layout.fillWidth: true | |
| 22 | 25 | spacing: 6 | |
| @@ -37,7 +40,8 @@ ColumnLayout { | |||
| 37 | 40 | Lb { | |
| 38 | 41 | visible: Gpu.pending !== "" || Gpu.message !== "" || Gpu.busy | |
| 39 | 42 | Layout.fillWidth: true | |
| 40 | − | Layout.leftMargin: 6 | |
| 43 | + | Layout.leftMargin: root.cards ? 18 : 6 | |
| 44 | + | Layout.bottomMargin: root.cards ? 8 : 0 | |
| 41 | 45 | wrapMode: Text.Wrap | |
| 42 | 46 | px: 10 | |
| 43 | 47 | color: Gpu.message !== "" ? Theme.err : Theme.live | |
| @@ -0,0 +1,41 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // A settings group: the caps title, then its rows on a rounded s1 card (Android / Nothing OS settings). | |
| 5 | + | // flat = the title and rows without the card (pages shared with the control panel, which already is a card). | |
| 6 | + | // SetIndex reads a literal `title:` (+ `keywords:`) as the group, like a SectionLabel's `text:`. | |
| 7 | + | ColumnLayout { | |
| 8 | + | id: g | |
| 9 | + | property string title: "" | |
| 10 | + | property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source) | |
| 11 | + | property string count: "" | |
| 12 | + | property bool flat: false | |
| 13 | + | property alias gap: body.spacing | |
| 14 | + | default property alias rows: body.data | |
| 15 | + | ||
| 16 | + | readonly property int pad: flat ? 0 : 8 | |
| 17 | + | ||
| 18 | + | Layout.fillWidth: true | |
| 19 | + | Layout.fillHeight: false // a layout's default is true; a page sets it for a list that scrolls inside | |
| 20 | + | spacing: flat ? 4 : 10 | |
| 21 | + | ||
| 22 | + | SectionLabel { | |
| 23 | + | visible: g.title !== "" | |
| 24 | + | Layout.leftMargin: g.flat ? 0 : 14 | |
| 25 | + | text: g.title | |
| 26 | + | count: g.count | |
| 27 | + | } | |
| 28 | + | Rectangle { | |
| 29 | + | Layout.fillWidth: true | |
| 30 | + | Layout.fillHeight: g.Layout.fillHeight // only when the group itself fills (a list that scrolls inside) | |
| 31 | + | implicitHeight: body.implicitHeight + 2 * g.pad | |
| 32 | + | radius: 28 + g.pad | |
| 33 | + | color: g.flat ? "transparent" : Theme.s1 | |
| 34 | + | ColumnLayout { | |
| 35 | + | id: body | |
| 36 | + | anchors.fill: parent | |
| 37 | + | anchors.margins: g.pad | |
| 38 | + | spacing: 4 | |
| 39 | + | } | |
| 40 | + | } | |
| 41 | + | } | |
| @@ -6,18 +6,19 @@ import QtQuick.Layouts | |||
| 6 | 6 | ColumnLayout { | |
| 7 | 7 | id: page | |
| 8 | 8 | property bool active: false | |
| 9 | + | property bool cards: false // the settings app: groups on cards | |
| 9 | 10 | onActiveChanged: Net.active = active | |
| 10 | 11 | // the settings app creates this page already active and destroys it when leaving the section | |
| 11 | 12 | Component.onCompleted: if (active) Net.active = true | |
| 12 | 13 | Component.onDestruction: if (active) Net.active = false | |
| 13 | − | spacing: 10 | |
| 14 | + | spacing: cards ? 22 : 10 | |
| 14 | 15 | ||
| 15 | 16 | // wired / usb tethering (read-only status, plus a disconnect button) | |
| 16 | − | ColumnLayout { | |
| 17 | + | Group { | |
| 17 | 18 | visible: Net.wiredDevs.length > 0 | |
| 18 | − | Layout.fillWidth: true | |
| 19 | − | spacing: 4 | |
| 20 | − | SectionLabel { text: "wired"; keywords: "ethernet lan cable usb tethering phone" } | |
| 19 | + | flat: !page.cards | |
| 20 | + | title: "wired" | |
| 21 | + | keywords: "ethernet lan cable usb tethering phone" | |
| 21 | 22 | Repeater { | |
| 22 | 23 | model: Net.wiredDevs | |
| 23 | 24 | delegate: ListRow { | |
| @@ -43,157 +44,173 @@ ColumnLayout { | |||
| 43 | 44 | } | |
| 44 | 45 | } | |
| 45 | 46 | ||
| 46 | − | RowLayout { | |
| 47 | − | Layout.fillWidth: true | |
| 48 | − | Layout.leftMargin: 6 | |
| 49 | − | spacing: 8 | |
| 50 | − | Lb { | |
| 47 | + | // the radio: a card of its own in the settings app (Android's "use Wi-Fi") | |
| 48 | + | Group { | |
| 49 | + | flat: !page.cards | |
| 50 | + | gap: 10 | |
| 51 | + | RowLayout { | |
| 51 | 52 | Layout.fillWidth: true | |
| 52 | − | elide: Text.ElideRight | |
| 53 | − | text: !Net.wifiEnabled ? "wi-fi · off" | |
| 54 | − | : (Net.connected !== "" ? "wi-fi · " + Net.connected : "wi-fi · not connected") | |
| 55 | − | color: Net.connected !== "" && Net.wifiEnabled ? Theme.fg : Theme.grey | |
| 56 | − | } | |
| 57 | − | IconBtn { | |
| 58 | − | icon: Theme.iRefresh | |
| 59 | − | active: Net.scanning | |
| 60 | − | onClicked: Net.rescan() | |
| 61 | − | } | |
| 62 | − | IconBtn { | |
| 63 | − | icon: Net.wifiEnabled ? Theme.iWifi : Theme.iWifiOff | |
| 64 | − | active: Net.wifiEnabled | |
| 65 | − | onClicked: Net.setWifi(!Net.wifiEnabled) | |
| 53 | + | Layout.leftMargin: page.cards ? 18 : 6 | |
| 54 | + | Layout.preferredHeight: page.cards ? 56 : -1 | |
| 55 | + | spacing: 8 | |
| 56 | + | Lb { | |
| 57 | + | Layout.fillWidth: true | |
| 58 | + | elide: Text.ElideRight | |
| 59 | + | text: !Net.wifiEnabled ? "wi-fi · off" | |
| 60 | + | : (Net.connected !== "" ? "wi-fi · " + Net.connected : "wi-fi · not connected") | |
| 61 | + | color: Net.connected !== "" && Net.wifiEnabled ? Theme.fg : Theme.grey | |
| 62 | + | } | |
| 63 | + | IconBtn { | |
| 64 | + | icon: Theme.iRefresh | |
| 65 | + | active: Net.scanning | |
| 66 | + | onClicked: Net.rescan() | |
| 67 | + | } | |
| 68 | + | IconBtn { | |
| 69 | + | icon: Net.wifiEnabled ? Theme.iWifi : Theme.iWifiOff | |
| 70 | + | active: Net.wifiEnabled | |
| 71 | + | onClicked: Net.setWifi(!Net.wifiEnabled) | |
| 72 | + | } | |
| 66 | 73 | } | |
| 67 | − | } | |
| 68 | 74 | ||
| 69 | − | Lb { | |
| 70 | − | visible: Net.message !== "" | |
| 71 | − | Layout.fillWidth: true | |
| 72 | − | Layout.leftMargin: 6 | |
| 73 | − | text: Net.message | |
| 74 | − | wrapMode: Text.WordWrap | |
| 75 | − | px: 10 | |
| 76 | − | color: Theme.err | |
| 75 | + | Lb { | |
| 76 | + | visible: Net.message !== "" | |
| 77 | + | Layout.fillWidth: true | |
| 78 | + | Layout.leftMargin: page.cards ? 18 : 6 | |
| 79 | + | Layout.bottomMargin: page.cards ? 10 : 0 | |
| 80 | + | text: Net.message | |
| 81 | + | wrapMode: Text.WordWrap | |
| 82 | + | px: 10 | |
| 83 | + | color: Theme.err | |
| 84 | + | } | |
| 77 | 85 | } | |
| 78 | 86 | ||
| 79 | − | SectionLabel { text: Net.busy ? "working…" : "networks"; count: Net.busy || !Net.wifiEnabled ? "" : String(Net.networks.length) } | |
| 80 | − | ||
| 81 | − | Flickable { | |
| 82 | − | id: flick | |
| 83 | − | Layout.fillWidth: true | |
| 87 | + | Group { | |
| 88 | + | title: Net.busy ? "working…" : "networks" | |
| 89 | + | count: Net.busy || !Net.wifiEnabled ? "" : String(Net.networks.length) | |
| 90 | + | flat: !page.cards | |
| 91 | + | spacing: 10 | |
| 84 | 92 | Layout.fillHeight: true | |
| 85 | − | Layout.preferredHeight: list.implicitHeight | |
| 86 | − | clip: true | |
| 87 | − | contentWidth: width | |
| 88 | − | contentHeight: list.implicitHeight | |
| 89 | − | boundsBehavior: Flickable.StopAtBounds | |
| 93 | + | // on a card: only as tall as the list, it scrolls inside once the page is full | |
| 94 | + | Layout.maximumHeight: page.cards ? implicitHeight : Infinity | |
| 95 | + | ||
| 96 | + | Flickable { | |
| 97 | + | id: flick | |
| 98 | + | Layout.fillWidth: true | |
| 99 | + | Layout.fillHeight: true | |
| 100 | + | Layout.preferredHeight: list.implicitHeight | |
| 101 | + | clip: true | |
| 102 | + | contentWidth: width | |
| 103 | + | contentHeight: list.implicitHeight | |
| 104 | + | boundsBehavior: Flickable.StopAtBounds | |
| 90 | 105 | ||
| 91 | − | ColumnLayout { | |
| 92 | − | id: list | |
| 93 | − | width: flick.width | |
| 94 | − | spacing: 2 | |
| 106 | + | ColumnLayout { | |
| 107 | + | id: list | |
| 108 | + | width: flick.width | |
| 109 | + | spacing: 2 | |
| 95 | 110 | ||
| 96 | − | Repeater { | |
| 97 | − | model: Net.wifiEnabled ? Net.networks : [] | |
| 98 | − | delegate: ColumnLayout { | |
| 99 | − | id: item | |
| 100 | − | required property var modelData | |
| 101 | − | Layout.fillWidth: true | |
| 102 | − | spacing: 4 | |
| 111 | + | Repeater { | |
| 112 | + | model: Net.wifiEnabled ? Net.networks : [] | |
| 113 | + | delegate: ColumnLayout { | |
| 114 | + | id: item | |
| 115 | + | required property var modelData | |
| 116 | + | Layout.fillWidth: true | |
| 117 | + | spacing: 4 | |
| 103 | 118 | ||
| 104 | − | ListRow { | |
| 105 | − | id: row | |
| 106 | − | icon: Theme.wifiIcon(item.modelData.signal) | |
| 107 | − | title: item.modelData.ssid | |
| 108 | − | readonly property bool portal: item.modelData.inUse && Net.portal && Net.portalNet === item.modelData.ssid | |
| 109 | − | subtitle: portal ? "sign in required" | |
| 110 | − | : item.modelData.inUse ? "connected" | |
| 111 | − | : (item.modelData.saved ? "saved" | |
| 112 | − | : (item.modelData.security === "" ? "open" : item.modelData.security)) | |
| 113 | − | trailing: item.modelData.signal + "%" | |
| 114 | − | current: item.modelData.inUse | |
| 115 | − | onClicked: Net.connectTo(item.modelData) | |
| 119 | + | ListRow { | |
| 120 | + | id: row | |
| 121 | + | icon: Theme.wifiIcon(item.modelData.signal) | |
| 122 | + | title: item.modelData.ssid | |
| 123 | + | readonly property bool portal: item.modelData.inUse && Net.portal && Net.portalNet === item.modelData.ssid | |
| 124 | + | subtitle: portal ? "sign in required" | |
| 125 | + | : item.modelData.inUse ? "connected" | |
| 126 | + | : (item.modelData.saved ? "saved" | |
| 127 | + | : (item.modelData.security === "" ? "open" : item.modelData.security)) | |
| 128 | + | trailing: item.modelData.signal + "%" | |
| 129 | + | current: item.modelData.inUse | |
| 130 | + | onClicked: Net.connectTo(item.modelData) | |
| 116 | 131 | ||
| 117 | − | IconBtn { | |
| 118 | − | visible: row.portal | |
| 119 | − | icon: Theme.iLogin | |
| 120 | − | implicitHeight: 32 | |
| 121 | − | onClicked: Net.openPortal() | |
| 122 | − | } | |
| 123 | − | IconBtn { | |
| 124 | − | visible: item.modelData.inUse | |
| 125 | − | icon: Theme.iClose | |
| 126 | − | implicitHeight: 32 | |
| 127 | − | onClicked: Net.disconnect() | |
| 128 | − | } | |
| 129 | − | IconBtn { | |
| 130 | − | visible: item.modelData.saved | |
| 131 | − | icon: Theme.iTrash | |
| 132 | − | implicitHeight: 32 | |
| 133 | − | onClicked: Net.forget(item.modelData.ssid) | |
| 132 | + | IconBtn { | |
| 133 | + | visible: row.portal | |
| 134 | + | icon: Theme.iLogin | |
| 135 | + | implicitHeight: 32 | |
| 136 | + | onClicked: Net.openPortal() | |
| 137 | + | } | |
| 138 | + | IconBtn { | |
| 139 | + | visible: item.modelData.inUse | |
| 140 | + | icon: Theme.iClose | |
| 141 | + | implicitHeight: 32 | |
| 142 | + | onClicked: Net.disconnect() | |
| 143 | + | } | |
| 144 | + | IconBtn { | |
| 145 | + | visible: item.modelData.saved | |
| 146 | + | icon: Theme.iTrash | |
| 147 | + | implicitHeight: 32 | |
| 148 | + | onClicked: Net.forget(item.modelData.ssid) | |
| 149 | + | } | |
| 134 | 150 | } | |
| 135 | − | } | |
| 136 | 151 | ||
| 137 | − | // password for a new secured network: a pill with white dots and the accent caret | |
| 138 | − | Rectangle { | |
| 139 | − | id: pwBox | |
| 140 | − | visible: Net.askFor === item.modelData.ssid | |
| 141 | − | Layout.fillWidth: true | |
| 142 | − | Layout.preferredHeight: visible ? 52 : 0 | |
| 143 | − | radius: height / 2 | |
| 144 | − | color: Theme.s2 | |
| 145 | − | border.width: 1 | |
| 146 | − | border.color: pw.activeFocus ? Theme.fill : Theme.s3 | |
| 147 | − | onVisibleChanged: if (visible) { pw.text = ""; pw.forceActiveFocus() } | |
| 152 | + | // password for a new secured network: a pill with white dots and the accent caret | |
| 153 | + | Rectangle { | |
| 154 | + | id: pwBox | |
| 155 | + | visible: Net.askFor === item.modelData.ssid | |
| 156 | + | Layout.fillWidth: true | |
| 157 | + | Layout.preferredHeight: visible ? 52 : 0 | |
| 158 | + | radius: height / 2 | |
| 159 | + | color: Theme.s2 | |
| 160 | + | border.width: 1 | |
| 161 | + | border.color: pw.activeFocus ? Theme.fill : Theme.s3 | |
| 162 | + | onVisibleChanged: if (visible) { pw.text = ""; pw.forceActiveFocus() } | |
| 148 | 163 | ||
| 149 | − | TextInput { | |
| 150 | − | id: pw | |
| 151 | − | anchors.left: parent.left | |
| 152 | − | anchors.leftMargin: 22 | |
| 153 | − | anchors.right: go.left | |
| 154 | − | anchors.rightMargin: 10 | |
| 155 | − | anchors.verticalCenter: parent.verticalCenter | |
| 156 | − | echoMode: TextInput.Password | |
| 157 | − | passwordCharacter: "•" | |
| 158 | − | color: Theme.fg | |
| 159 | − | selectionColor: Theme.fill | |
| 160 | − | selectedTextColor: Theme.fillFg | |
| 161 | − | font.family: Theme.mono | |
| 162 | − | font.pixelSize: 15 | |
| 163 | − | font.letterSpacing: 2 | |
| 164 | − | clip: true | |
| 165 | − | cursorDelegate: Rectangle { | |
| 166 | − | width: 2 | |
| 167 | − | color: Theme.live | |
| 168 | − | visible: pw.activeFocus | |
| 169 | − | Timer { | |
| 170 | − | running: pw.activeFocus | |
| 171 | − | interval: 500 | |
| 172 | − | repeat: true | |
| 173 | − | onTriggered: parent.opacity = 1 - parent.opacity | |
| 164 | + | TextInput { | |
| 165 | + | id: pw | |
| 166 | + | anchors.left: parent.left | |
| 167 | + | anchors.leftMargin: 22 | |
| 168 | + | anchors.right: go.left | |
| 169 | + | anchors.rightMargin: 10 | |
| 170 | + | anchors.verticalCenter: parent.verticalCenter | |
| 171 | + | echoMode: TextInput.Password | |
| 172 | + | passwordCharacter: "•" | |
| 173 | + | color: Theme.fg | |
| 174 | + | selectionColor: Theme.fill | |
| 175 | + | selectedTextColor: Theme.fillFg | |
| 176 | + | font.family: Theme.mono | |
| 177 | + | font.pixelSize: 15 | |
| 178 | + | font.letterSpacing: 2 | |
| 179 | + | clip: true | |
| 180 | + | cursorDelegate: Rectangle { | |
| 181 | + | width: 2 | |
| 182 | + | color: Theme.live | |
| 183 | + | visible: pw.activeFocus | |
| 184 | + | Timer { | |
| 185 | + | running: pw.activeFocus | |
| 186 | + | interval: 500 | |
| 187 | + | repeat: true | |
| 188 | + | onTriggered: parent.opacity = 1 - parent.opacity | |
| 189 | + | } | |
| 190 | + | } | |
| 191 | + | onAccepted: Net.connect(item.modelData.ssid, text) | |
| 192 | + | Lb { | |
| 193 | + | visible: !pw.text | |
| 194 | + | anchors.verticalCenter: parent.verticalCenter | |
| 195 | + | text: "password" | |
| 196 | + | color: Theme.mute | |
| 174 | 197 | } | |
| 175 | 198 | } | |
| 176 | − | onAccepted: Net.connect(item.modelData.ssid, text) | |
| 177 | − | Lb { | |
| 178 | − | visible: !pw.text | |
| 199 | + | IconBtn { | |
| 200 | + | id: go | |
| 201 | + | anchors.right: parent.right | |
| 202 | + | anchors.rightMargin: 6 | |
| 179 | 203 | anchors.verticalCenter: parent.verticalCenter | |
| 180 | − | text: "password" | |
| 181 | − | color: Theme.mute | |
| 204 | + | implicitHeight: 40 | |
| 205 | + | icon: Theme.iCheck | |
| 206 | + | active: pw.text !== "" | |
| 207 | + | onClicked: Net.connect(item.modelData.ssid, pw.text) | |
| 182 | 208 | } | |
| 183 | 209 | } | |
| 184 | − | IconBtn { | |
| 185 | − | id: go | |
| 186 | − | anchors.right: parent.right | |
| 187 | − | anchors.rightMargin: 6 | |
| 188 | − | anchors.verticalCenter: parent.verticalCenter | |
| 189 | − | implicitHeight: 40 | |
| 190 | − | icon: Theme.iCheck | |
| 191 | − | active: pw.text !== "" | |
| 192 | − | onClicked: Net.connect(item.modelData.ssid, pw.text) | |
| 193 | − | } | |
| 194 | 210 | } | |
| 195 | 211 | } | |
| 196 | 212 | } | |
| 197 | 213 | } | |
| 198 | 214 | } | |
| 215 | + | Item { visible: page.cards; Layout.fillHeight: true } | |
| 199 | 216 | } | |
| @@ -43,90 +43,118 @@ Flickable { | |||
| 43 | 43 | ColumnLayout { | |
| 44 | 44 | id: col | |
| 45 | 45 | width: flick.width | |
| 46 | − | spacing: 8 | |
| 46 | + | spacing: 22 | |
| 47 | 47 | ||
| 48 | − | RowLayout { | |
| 49 | − | spacing: 14 | |
| 50 | − | Text { | |
| 51 | − | text: Theme.iArch | |
| 52 | − | color: Theme.fill | |
| 53 | − | font.family: Theme.ui | |
| 54 | − | font.pixelSize: 56 | |
| 55 | − | } | |
| 56 | − | ColumnLayout { | |
| 57 | − | spacing: 2 | |
| 48 | + | // the machine at a glance, like Android's "about phone" header | |
| 49 | + | Group { | |
| 50 | + | RowLayout { | |
| 51 | + | Layout.margins: 14 | |
| 52 | + | Layout.leftMargin: 22 | |
| 53 | + | spacing: 18 | |
| 58 | 54 | Text { | |
| 59 | − | text: flick.info.length ? flick.info[0][1] : "" | |
| 60 | − | color: Theme.fg | |
| 55 | + | text: Theme.iArch | |
| 56 | + | color: Theme.fill | |
| 61 | 57 | font.family: Theme.ui | |
| 62 | − | font.pixelSize: 21 | |
| 63 | − | font.bold: true | |
| 58 | + | font.pixelSize: 56 | |
| 64 | 59 | } | |
| 65 | − | Text { | |
| 66 | − | text: Quickshell.env("USER") + "@" + (flick.info.length > 2 ? flick.info[2][1] : "") | |
| 67 | − | + " · up " + Theme.fmtTime(SysStats.uptime) | |
| 68 | − | color: Theme.fgDim | |
| 69 | − | font.family: Theme.ui | |
| 70 | − | font.pixelSize: 15 | |
| 60 | + | ColumnLayout { | |
| 61 | + | spacing: 2 | |
| 62 | + | Text { | |
| 63 | + | text: flick.info.length ? flick.info[0][1] : "" | |
| 64 | + | color: Theme.fg | |
| 65 | + | font.family: Theme.ui | |
| 66 | + | font.pixelSize: 21 | |
| 67 | + | font.bold: true | |
| 68 | + | } | |
| 69 | + | Text { | |
| 70 | + | text: Quickshell.env("USER") + "@" + (flick.info.length > 2 ? flick.info[2][1] : "") | |
| 71 | + | + " · up " + Theme.fmtTime(SysStats.uptime) | |
| 72 | + | color: Theme.fgDim | |
| 73 | + | font.family: Theme.ui | |
| 74 | + | font.pixelSize: 15 | |
| 75 | + | } | |
| 71 | 76 | } | |
| 72 | 77 | } | |
| 73 | 78 | } | |
| 74 | 79 | ||
| 75 | − | SectionLabel { text: "system"; keywords: "kernel cpu gpu host hostname distro packages version uptime"; Layout.topMargin: 6 } | |
| 76 | − | Repeater { | |
| 77 | − | model: flick.info | |
| 78 | − | delegate: RowLayout { | |
| 79 | − | required property var modelData | |
| 80 | − | Layout.fillWidth: true | |
| 81 | − | spacing: 8 | |
| 82 | − | Text { | |
| 83 | − | Layout.preferredWidth: 150 | |
| 84 | − | text: modelData[0] | |
| 85 | − | color: Theme.fill | |
| 86 | − | font.family: Theme.ui | |
| 87 | − | font.pixelSize: 15 | |
| 88 | − | } | |
| 89 | − | Text { | |
| 80 | + | Group { | |
| 81 | + | title: "system" | |
| 82 | + | keywords: "kernel cpu gpu host hostname distro packages version uptime" | |
| 83 | + | gap: 0 | |
| 84 | + | Repeater { | |
| 85 | + | model: flick.info | |
| 86 | + | delegate: RowLayout { | |
| 87 | + | required property var modelData | |
| 90 | 88 | Layout.fillWidth: true | |
| 91 | − | Layout.leftMargin: 18 | |
| 92 | − | text: modelData[1] | |
| 93 | − | color: Theme.fg | |
| 94 | − | elide: Text.ElideRight | |
| 95 | − | font.family: Theme.ui | |
| 96 | − | font.pixelSize: 15 | |
| 89 | + | Layout.preferredHeight: 40 | |
| 90 | + | spacing: 8 | |
| 91 | + | Text { | |
| 92 | + | Layout.preferredWidth: 150 | |
| 93 | + | Layout.leftMargin: 18 | |
| 94 | + | text: modelData[0] | |
| 95 | + | color: Theme.fill | |
| 96 | + | font.family: Theme.ui | |
| 97 | + | font.pixelSize: 15 | |
| 98 | + | } | |
| 99 | + | Text { | |
| 100 | + | Layout.fillWidth: true | |
| 101 | + | Layout.leftMargin: 18 | |
| 102 | + | text: modelData[1] | |
| 103 | + | color: Theme.fg | |
| 104 | + | elide: Text.ElideRight | |
| 105 | + | font.family: Theme.ui | |
| 106 | + | font.pixelSize: 15 | |
| 107 | + | } | |
| 97 | 108 | } | |
| 98 | 109 | } | |
| 99 | 110 | } | |
| 100 | 111 | ||
| 101 | − | SectionLabel { text: "memory & storage"; keywords: "ram disk space drives usage"; Layout.topMargin: 6 } | |
| 102 | − | StatBar { | |
| 103 | − | label: "memory" | |
| 104 | − | frac: SysStats.memTotal > 0 ? SysStats.memUsed / SysStats.memTotal : 0 | |
| 105 | − | valueText: flick.size(SysStats.memUsed) + " / " + flick.size(SysStats.memTotal) | |
| 106 | − | } | |
| 107 | − | Repeater { | |
| 108 | − | model: SysStats.disks | |
| 109 | − | delegate: StatBar { | |
| 110 | − | required property var modelData | |
| 111 | − | label: modelData.label | |
| 112 | − | frac: modelData.used / modelData.size | |
| 113 | − | fill: frac > 0.9 ? Theme.err : Theme.fill | |
| 114 | − | valueText: flick.size(modelData.used) + " / " + flick.size(modelData.size) | |
| 112 | + | Group { | |
| 113 | + | title: "memory & storage" | |
| 114 | + | keywords: "ram disk space drives usage" | |
| 115 | + | gap: 14 | |
| 116 | + | StatBar { | |
| 117 | + | Layout.leftMargin: 18 | |
| 118 | + | Layout.rightMargin: 18 | |
| 119 | + | Layout.topMargin: 12 | |
| 120 | + | label: "memory" | |
| 121 | + | frac: SysStats.memTotal > 0 ? SysStats.memUsed / SysStats.memTotal : 0 | |
| 122 | + | valueText: flick.size(SysStats.memUsed) + " / " + flick.size(SysStats.memTotal) | |
| 123 | + | } | |
| 124 | + | Repeater { | |
| 125 | + | model: SysStats.disks | |
| 126 | + | delegate: StatBar { | |
| 127 | + | required property var modelData | |
| 128 | + | Layout.leftMargin: 18 | |
| 129 | + | Layout.rightMargin: 18 | |
| 130 | + | label: modelData.label | |
| 131 | + | frac: modelData.used / modelData.size | |
| 132 | + | fill: frac > 0.9 ? Theme.err : Theme.fill | |
| 133 | + | valueText: flick.size(modelData.used) + " / " + flick.size(modelData.size) | |
| 134 | + | } | |
| 115 | 135 | } | |
| 136 | + | Item { Layout.preferredHeight: 2 } | |
| 116 | 137 | } | |
| 117 | 138 | ||
| 118 | − | SectionLabel { text: "credits"; keywords: "license glyph-rice fonts"; Layout.topMargin: 6 } | |
| 119 | − | Text { | |
| 120 | − | Layout.fillWidth: true | |
| 121 | − | Layout.leftMargin: 18 | |
| 122 | − | wrapMode: Text.WordWrap | |
| 123 | − | lineHeight: 1.3 | |
| 124 | − | color: Theme.fgDim | |
| 125 | − | font.family: Theme.ui | |
| 126 | − | font.pixelSize: 14 | |
| 127 | − | text: "The dot-matrix shaders and some components started as ports from glyph-rice by Nebelung-cloud " | |
| 128 | − | + "(GPL-3.0). Fonts: Doto, Space Grotesk, Space Mono (SIL OFL). Colours: matugen (Material You). Nothing, Nothing OS and Glyph are trademarks of Nothing Technology Ltd; " | |
| 129 | − | + "this is a fan project, not affiliated with Nothing." | |
| 139 | + | Group { | |
| 140 | + | title: "credits" | |
| 141 | + | keywords: "license glyph-rice fonts" | |
| 142 | + | Text { | |
| 143 | + | Layout.fillWidth: true | |
| 144 | + | Layout.leftMargin: 18 | |
| 145 | + | Layout.rightMargin: 18 | |
| 146 | + | Layout.topMargin: 10 | |
| 147 | + | Layout.bottomMargin: 10 | |
| 148 | + | wrapMode: Text.WordWrap | |
| 149 | + | lineHeight: 1.3 | |
| 150 | + | color: Theme.fgDim | |
| 151 | + | font.family: Theme.ui | |
| 152 | + | font.pixelSize: 14 | |
| 153 | + | text: "The dot-matrix shaders and some components started as ports from glyph-rice by Nebelung-cloud " | |
| 154 | + | + "(GPL-3.0). Fonts: Doto, Space Grotesk, Space Mono (SIL OFL). Colours: matugen (Material You). Nothing, Nothing OS and Glyph are trademarks of Nothing Technology Ltd; " | |
| 155 | + | + "this is a fan project, not affiliated with Nothing." | |
| 156 | + | } | |
| 130 | 157 | } | |
| 158 | + | ||
| 131 | 159 | } | |
| 132 | 160 | } | |
| @@ -15,130 +15,159 @@ Flickable { | |||
| 15 | 15 | ColumnLayout { | |
| 16 | 16 | id: col | |
| 17 | 17 | width: flick.width | |
| 18 | − | spacing: 10 | |
| 18 | + | spacing: 22 | |
| 19 | 19 | ||
| 20 | − | SectionLabel { text: "wallpaper"; keywords: "background image picture" } | |
| 21 | − | Text { | |
| 22 | − | Layout.fillWidth: true | |
| 23 | − | Layout.leftMargin: 18 | |
| 24 | − | text: Wall.current !== "" ? Wall.current.replace(Wall.home, "~") : "-" | |
| 25 | − | elide: Text.ElideLeft | |
| 26 | − | color: Theme.fgDim | |
| 27 | − | font.family: Theme.ui | |
| 28 | − | font.pixelSize: 14 | |
| 29 | − | } | |
| 30 | − | RowLayout { | |
| 31 | − | Layout.fillWidth: true | |
| 32 | − | spacing: 6 | |
| 33 | − | IconBtn { | |
| 20 | + | Group { | |
| 21 | + | title: "wallpaper" | |
| 22 | + | keywords: "background image picture" | |
| 23 | + | gap: 10 | |
| 24 | + | Text { | |
| 34 | 25 | Layout.fillWidth: true | |
| 35 | − | icon: Theme.iImage | |
| 36 | − | label: "open picker" | |
| 37 | − | keywords: "wallpaper picker choose background" | |
| 38 | − | onClicked: flick.overlayRequested("wall") | |
| 26 | + | Layout.leftMargin: 18 | |
| 27 | + | Layout.rightMargin: 18 | |
| 28 | + | Layout.topMargin: 8 | |
| 29 | + | text: Wall.current !== "" ? Wall.current.replace(Wall.home, "~") : "-" | |
| 30 | + | elide: Text.ElideLeft | |
| 31 | + | color: Theme.fgDim | |
| 32 | + | font.family: Theme.ui | |
| 33 | + | font.pixelSize: 14 | |
| 39 | 34 | } | |
| 40 | − | IconBtn { | |
| 35 | + | RowLayout { | |
| 41 | 36 | Layout.fillWidth: true | |
| 42 | − | icon: Theme.iShuffle | |
| 43 | − | label: "random from folder" | |
| 44 | − | keywords: "shuffle random wallpaper background" | |
| 45 | − | onClicked: Wall.random() | |
| 37 | + | spacing: 6 | |
| 38 | + | IconBtn { | |
| 39 | + | Layout.fillWidth: true | |
| 40 | + | icon: Theme.iImage | |
| 41 | + | label: "open picker" | |
| 42 | + | keywords: "wallpaper picker choose background" | |
| 43 | + | onClicked: flick.overlayRequested("wall") | |
| 44 | + | } | |
| 45 | + | IconBtn { | |
| 46 | + | Layout.fillWidth: true | |
| 47 | + | icon: Theme.iShuffle | |
| 48 | + | label: "random from folder" | |
| 49 | + | keywords: "shuffle random wallpaper background" | |
| 50 | + | onClicked: Wall.random() | |
| 51 | + | } | |
| 46 | 52 | } | |
| 47 | − | } | |
| 48 | − | Text { | |
| 49 | − | text: "random picks from" | |
| 50 | − | color: Theme.fg | |
| 51 | − | font.family: Theme.ui | |
| 52 | − | font.pixelSize: 15 | |
| 53 | − | } | |
| 54 | − | Flow { | |
| 55 | − | Layout.fillWidth: true | |
| 56 | − | spacing: 4 | |
| 57 | − | Repeater { | |
| 58 | − | model: [""].concat(Wall.folders) | |
| 59 | − | delegate: IconBtn { | |
| 60 | − | required property string modelData | |
| 61 | − | implicitHeight: 26 | |
| 62 | − | label: modelData === "" ? "all" : modelData | |
| 63 | − | active: Settings.wallFolder === modelData | |
| 64 | − | onClicked: Settings.wallFolder = modelData | |
| 53 | + | Text { | |
| 54 | + | Layout.leftMargin: 18 | |
| 55 | + | Layout.topMargin: 4 | |
| 56 | + | text: "random picks from" | |
| 57 | + | color: Theme.fg | |
| 58 | + | font.family: Theme.ui | |
| 59 | + | font.pixelSize: 15 | |
| 60 | + | } | |
| 61 | + | Flow { | |
| 62 | + | Layout.fillWidth: true | |
| 63 | + | Layout.leftMargin: 14 | |
| 64 | + | Layout.rightMargin: 14 | |
| 65 | + | Layout.bottomMargin: 8 | |
| 66 | + | spacing: 4 | |
| 67 | + | Repeater { | |
| 68 | + | model: [""].concat(Wall.folders) | |
| 69 | + | delegate: IconBtn { | |
| 70 | + | required property string modelData | |
| 71 | + | implicitHeight: 26 | |
| 72 | + | label: modelData === "" ? "all" : modelData | |
| 73 | + | active: Settings.wallFolder === modelData | |
| 74 | + | onClicked: Settings.wallFolder = modelData | |
| 75 | + | } | |
| 65 | 76 | } | |
| 66 | 77 | } | |
| 67 | 78 | } | |
| 68 | 79 | ||
| 69 | − | SectionLabel { text: "wallpaper look"; keywords: "wallpaper style halftone" } | |
| 70 | − | Choice { | |
| 71 | − | label: "drawn as" | |
| 72 | − | keywords: "wallpaper style look" | |
| 73 | − | options: [{ value: "dots", label: "mono dots" }, { value: "colour", label: "colour dots" }, { value: "photo", label: "photo" }] | |
| 74 | − | value: Settings.wallLook | |
| 75 | − | onPicked: v => Settings.wallLook = v | |
| 80 | + | Group { | |
| 81 | + | title: "wallpaper look" | |
| 82 | + | keywords: "wallpaper style halftone" | |
| 83 | + | Choice { | |
| 84 | + | label: "drawn as" | |
| 85 | + | keywords: "wallpaper style look" | |
| 86 | + | options: [{ value: "dots", label: "mono dots" }, { value: "colour", label: "colour dots" }, { value: "photo", label: "photo" }] | |
| 87 | + | value: Settings.wallLook | |
| 88 | + | onPicked: v => Settings.wallLook = v | |
| 89 | + | } | |
| 90 | + | Knob { label: "dot size"; keywords: "pitch grid"; from: 6; to: 24; value: Settings.wallPitch; unit: "px"; onMoved: v => Settings.wallPitch = v } | |
| 91 | + | Knob { label: "brightness"; keywords: "wallpaper dim dark"; from: 20; to: 100; value: Settings.wallBright; unit: "%"; onMoved: v => Settings.wallBright = v } | |
| 92 | + | Switch { label: "dot grid"; hint: "on the desktop when there is no wallpaper"; checked: Settings.dotGrid; onToggled: on => Settings.dotGrid = on } | |
| 76 | 93 | } | |
| 77 | − | Knob { label: "dot size"; keywords: "pitch grid"; from: 6; to: 24; value: Settings.wallPitch; unit: "px"; onMoved: v => Settings.wallPitch = v } | |
| 78 | − | Knob { label: "brightness"; keywords: "wallpaper dim dark"; from: 20; to: 100; value: Settings.wallBright; unit: "%"; onMoved: v => Settings.wallBright = v } | |
| 79 | − | Switch { label: "dot grid"; hint: "on the desktop when there is no wallpaper"; checked: Settings.dotGrid; onToggled: on => Settings.dotGrid = on } | |
| 80 | 94 | ||
| 81 | − | SectionLabel { text: "desktop"; keywords: "home screen" } | |
| 82 | − | Switch { label: "24-hour clock"; keywords: "time format 12-hour am pm"; checked: Settings.clock24; onToggled: on => Settings.clock24 = on } | |
| 83 | − | Switch { label: "blinking colon"; keywords: "clock blink"; checked: Settings.blink; onToggled: on => Settings.blink = on } | |
| 84 | − | Switch { label: "animations"; keywords: "motion transitions reduce"; hint: "panels slide and fade, dots light up in waves"; checked: Settings.motion; onToggled: on => Settings.motion = on } | |
| 85 | − | Choice { | |
| 86 | − | label: "app icons" | |
| 87 | − | keywords: "dock icons" | |
| 88 | − | options: [{ value: "dots", label: "dots" }, { value: "mono", label: "mono" }, { value: "colour", label: "colour" }] | |
| 89 | − | value: Settings.appIcons | |
| 90 | − | onPicked: v => Settings.appIcons = v | |
| 95 | + | Group { | |
| 96 | + | title: "desktop" | |
| 97 | + | keywords: "home screen" | |
| 98 | + | Switch { label: "24-hour clock"; keywords: "time format 12-hour am pm"; checked: Settings.clock24; onToggled: on => Settings.clock24 = on } | |
| 99 | + | Switch { label: "blinking colon"; keywords: "clock blink"; checked: Settings.blink; onToggled: on => Settings.blink = on } | |
| 100 | + | Switch { label: "animations"; keywords: "motion transitions reduce"; hint: "panels slide and fade, dots light up in waves"; checked: Settings.motion; onToggled: on => Settings.motion = on } | |
| 101 | + | Choice { | |
| 102 | + | label: "app icons" | |
| 103 | + | keywords: "dock icons" | |
| 104 | + | options: [{ value: "dots", label: "dots" }, { value: "mono", label: "mono" }, { value: "colour", label: "colour" }] | |
| 105 | + | value: Settings.appIcons | |
| 106 | + | onPicked: v => Settings.appIcons = v | |
| 107 | + | } | |
| 91 | 108 | } | |
| 92 | 109 | ||
| 93 | − | SectionLabel { | |
| 94 | − | text: "colours" | |
| 110 | + | Group { | |
| 111 | + | title: "colours" | |
| 95 | 112 | keywords: "colors theme scheme accent palette matugen material tonal-spot vibrant expressive fidelity content fruit-salad rainbow neutral monochrome" | |
| 96 | − | } | |
| 97 | − | Text { | |
| 98 | − | Layout.fillWidth: true | |
| 99 | − | Layout.leftMargin: 18 | |
| 100 | − | text: "matugen scheme from the wallpaper, accent = its primary" | |
| 101 | − | elide: Text.ElideRight | |
| 102 | − | color: Theme.fgDim | |
| 103 | − | font.family: Theme.ui | |
| 104 | − | font.pixelSize: 14 | |
| 105 | − | } | |
| 106 | − | Flow { | |
| 107 | − | Layout.fillWidth: true | |
| 108 | − | spacing: 4 | |
| 109 | − | Repeater { | |
| 110 | − | model: ["tonal-spot", "vibrant", "expressive", "fidelity", "content", "fruit-salad", "rainbow", "neutral", "monochrome"] | |
| 111 | − | delegate: IconBtn { | |
| 112 | − | required property string modelData | |
| 113 | − | implicitHeight: 26 | |
| 114 | − | label: modelData | |
| 115 | − | active: Settings.matuScheme === "scheme-" + modelData | |
| 116 | − | onClicked: Settings.matuScheme = "scheme-" + modelData | |
| 113 | + | gap: 10 | |
| 114 | + | Text { | |
| 115 | + | Layout.fillWidth: true | |
| 116 | + | Layout.leftMargin: 18 | |
| 117 | + | Layout.rightMargin: 18 | |
| 118 | + | Layout.topMargin: 8 | |
| 119 | + | text: "matugen scheme from the wallpaper, accent = its primary" | |
| 120 | + | elide: Text.ElideRight | |
| 121 | + | color: Theme.fgDim | |
| 122 | + | font.family: Theme.ui | |
| 123 | + | font.pixelSize: 14 | |
| 124 | + | } | |
| 125 | + | Flow { | |
| 126 | + | Layout.fillWidth: true | |
| 127 | + | Layout.leftMargin: 14 | |
| 128 | + | Layout.rightMargin: 14 | |
| 129 | + | spacing: 4 | |
| 130 | + | Repeater { | |
| 131 | + | model: ["tonal-spot", "vibrant", "expressive", "fidelity", "content", "fruit-salad", "rainbow", "neutral", "monochrome"] | |
| 132 | + | delegate: IconBtn { | |
| 133 | + | required property string modelData | |
| 134 | + | implicitHeight: 26 | |
| 135 | + | label: modelData | |
| 136 | + | active: Settings.matuScheme === "scheme-" + modelData | |
| 137 | + | onClicked: Settings.matuScheme = "scheme-" + modelData | |
| 138 | + | } | |
| 117 | 139 | } | |
| 118 | 140 | } | |
| 141 | + | Knob { label: "contrast"; keywords: "colours colors"; from: -1; to: 1; decimals: 2; value: Settings.matuContrast; onMoved: v => Settings.matuContrast = v } | |
| 119 | 142 | } | |
| 120 | − | Knob { label: "contrast"; keywords: "colours colors"; from: -1; to: 1; decimals: 2; value: Settings.matuContrast; onMoved: v => Settings.matuContrast = v } | |
| 121 | 143 | ||
| 122 | − | SectionLabel { text: "bar"; keywords: "status bar sizes" } | |
| 123 | − | Choice { | |
| 124 | − | label: "bar layout" | |
| 125 | − | keywords: "vertical horizontal orientation side left top position" | |
| 126 | − | options: [{ value: "horizontal", label: "horizontal" }, { value: "vertical", label: "vertical" }] | |
| 127 | − | value: Settings.barLayout | |
| 128 | − | onPicked: v => Settings.barLayout = v | |
| 129 | − | } | |
| 130 | − | Choice { | |
| 131 | − | label: "bar shape" | |
| 132 | − | keywords: "floating pill attached edge strip rounded" | |
| 133 | − | options: [{ value: false, label: "attached" }, { value: true, label: "floating pill" }] | |
| 134 | − | value: Settings.barFloat | |
| 135 | − | onPicked: v => Settings.barFloat = v | |
| 144 | + | Group { | |
| 145 | + | title: "bar" | |
| 146 | + | keywords: "status bar sizes" | |
| 147 | + | Choice { | |
| 148 | + | label: "bar layout" | |
| 149 | + | keywords: "vertical horizontal orientation side left top position" | |
| 150 | + | options: [{ value: "horizontal", label: "horizontal" }, { value: "vertical", label: "vertical" }] | |
| 151 | + | value: Settings.barLayout | |
| 152 | + | onPicked: v => Settings.barLayout = v | |
| 153 | + | } | |
| 154 | + | Choice { | |
| 155 | + | label: "bar shape" | |
| 156 | + | keywords: "floating pill attached edge strip rounded" | |
| 157 | + | options: [{ value: false, label: "attached" }, { value: true, label: "floating pill" }] | |
| 158 | + | value: Settings.barFloat | |
| 159 | + | onPicked: v => Settings.barFloat = v | |
| 160 | + | } | |
| 161 | + | Knob { visible: !Theme.barVertical; label: "bar height"; keywords: "top bar size"; from: 32; to: 56; value: Settings.barSize; onMoved: v => Settings.barSize = v } | |
| 162 | + | Knob { visible: Theme.barVertical; label: "bar width"; keywords: "vertical side bar size"; from: 48; to: 80; value: Settings.barWidth; onMoved: v => Settings.barWidth = v } | |
| 136 | 163 | } | |
| 137 | − | Knob { visible: !Theme.barVertical; label: "bar height"; keywords: "top bar size"; from: 32; to: 56; value: Settings.barSize; onMoved: v => Settings.barSize = v } | |
| 138 | − | Knob { visible: Theme.barVertical; label: "bar width"; keywords: "vertical side bar size"; from: 48; to: 80; value: Settings.barWidth; onMoved: v => Settings.barWidth = v } | |
| 139 | 164 | ||
| 140 | − | SectionLabel { text: "transparency"; keywords: "transparent opacity alpha translucent" } | |
| 141 | − | Knob { label: "bar opacity"; keywords: "transparent"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v } | |
| 142 | − | Knob { label: "panel opacity"; keywords: "transparent control panel"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v } | |
| 165 | + | Group { | |
| 166 | + | title: "transparency" | |
| 167 | + | keywords: "transparent opacity alpha translucent" | |
| 168 | + | Knob { label: "bar opacity"; keywords: "transparent"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v } | |
| 169 | + | Knob { label: "panel opacity"; keywords: "transparent control panel"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v } | |
| 170 | + | } | |
| 171 | + | Item { Layout.preferredHeight: 20 } | |
| 143 | 172 | } | |
| 144 | 173 | } | |
| @@ -140,19 +140,20 @@ Flickable { | |||
| 140 | 140 | ColumnLayout { | |
| 141 | 141 | id: col | |
| 142 | 142 | width: flick.width | |
| 143 | − | spacing: 8 | |
| 143 | + | spacing: 22 | |
| 144 | 144 | ||
| 145 | 145 | // keep / revert bar after a monitor change | |
| 146 | 146 | Rectangle { | |
| 147 | 147 | Layout.fillWidth: true | |
| 148 | 148 | visible: flick.countdown > 0 | |
| 149 | − | implicitHeight: 40 | |
| 149 | + | implicitHeight: 56 | |
| 150 | + | radius: height / 2 | |
| 150 | 151 | color: Theme.s2 | |
| 151 | 152 | border.width: 1 | |
| 152 | 153 | border.color: Theme.fill | |
| 153 | 154 | RowLayout { | |
| 154 | 155 | anchors.fill: parent | |
| 155 | − | anchors.margins: 6 | |
| 156 | + | anchors.margins: 10 | |
| 156 | 157 | Text { | |
| 157 | 158 | Layout.fillWidth: true | |
| 158 | 159 | Layout.leftMargin: 18 | |
| @@ -176,39 +177,46 @@ Flickable { | |||
| 176 | 177 | font.pixelSize: 14 | |
| 177 | 178 | } | |
| 178 | 179 | ||
| 179 | − | SectionLabel { text: "layout"; keywords: "monitors screens" } | |
| 180 | − | Choice { | |
| 181 | − | label: "screens" | |
| 182 | − | keywords: "monitors extend external laptop mirror" | |
| 183 | − | options: [{ value: "extend", label: "extend" }, { value: "laptop", label: "laptop only" }, | |
| 184 | − | { value: "external", label: "external only" }] | |
| 185 | − | value: flick.layout | |
| 186 | − | onPicked: x => flick.setLayout(x) | |
| 187 | − | } | |
| 188 | − | Text { | |
| 189 | − | Layout.fillWidth: true | |
| 190 | − | Layout.leftMargin: 18 | |
| 191 | − | text: "applies whenever a monitor is plugged in. external only turns the laptop panel back on when the last external screen is unplugged." | |
| 192 | − | color: Theme.fgDim | |
| 193 | − | wrapMode: Text.WordWrap | |
| 194 | − | font.family: Theme.ui | |
| 195 | − | font.pixelSize: 14 | |
| 180 | + | Group { | |
| 181 | + | title: "layout" | |
| 182 | + | keywords: "monitors screens" | |
| 183 | + | Choice { | |
| 184 | + | label: "screens" | |
| 185 | + | keywords: "monitors extend external laptop mirror" | |
| 186 | + | options: [{ value: "extend", label: "extend" }, { value: "laptop", label: "laptop only" }, | |
| 187 | + | { value: "external", label: "external only" }] | |
| 188 | + | value: flick.layout | |
| 189 | + | onPicked: x => flick.setLayout(x) | |
| 190 | + | } | |
| 191 | + | Text { | |
| 192 | + | Layout.fillWidth: true | |
| 193 | + | Layout.leftMargin: 18 | |
| 194 | + | Layout.rightMargin: 18 | |
| 195 | + | Layout.bottomMargin: 10 | |
| 196 | + | text: "applies whenever a monitor is plugged in. external only turns the laptop panel back on when the last external screen is unplugged." | |
| 197 | + | color: Theme.fgDim | |
| 198 | + | wrapMode: Text.WordWrap | |
| 199 | + | font.family: Theme.ui | |
| 200 | + | font.pixelSize: 14 | |
| 201 | + | } | |
| 196 | 202 | } | |
| 197 | 203 | ||
| 198 | 204 | Repeater { | |
| 199 | 205 | model: flick.monitors | |
| 200 | − | delegate: ColumnLayout { | |
| 206 | + | // one card per monitor | |
| 207 | + | delegate: Group { | |
| 201 | 208 | id: mon | |
| 202 | 209 | required property var modelData | |
| 203 | 210 | readonly property var m: modelData | |
| 204 | 211 | readonly property string curMode: m.width + "x" + m.height | |
| 205 | − | Layout.fillWidth: true | |
| 206 | − | spacing: 6 | |
| 212 | + | title: mon.m.name | |
| 213 | + | gap: 8 | |
| 207 | 214 | ||
| 208 | − | SectionLabel { text: mon.m.name } | |
| 209 | 215 | Text { | |
| 210 | 216 | Layout.fillWidth: true | |
| 211 | 217 | Layout.leftMargin: 18 | |
| 218 | + | Layout.rightMargin: 18 | |
| 219 | + | Layout.topMargin: 10 | |
| 212 | 220 | text: mon.m.description + (mon.m.disabled ? " · disabled" | |
| 213 | 221 | : " · " + mon.curMode + " @ " + mon.m.refreshRate.toFixed(2) + "Hz · scale " + mon.m.scale | |
| 214 | 222 | + " · at " + mon.m.x + "," + mon.m.y) | |
| @@ -219,6 +227,8 @@ Flickable { | |||
| 219 | 227 | } | |
| 220 | 228 | Text { | |
| 221 | 229 | visible: !mon.m.disabled | |
| 230 | + | Layout.leftMargin: 18 | |
| 231 | + | Layout.topMargin: 4 | |
| 222 | 232 | text: "mode" | |
| 223 | 233 | color: Theme.fg | |
| 224 | 234 | font.family: Theme.ui | |
| @@ -226,6 +236,8 @@ Flickable { | |||
| 226 | 236 | } | |
| 227 | 237 | Flow { | |
| 228 | 238 | Layout.fillWidth: true | |
| 239 | + | Layout.leftMargin: 14 | |
| 240 | + | Layout.rightMargin: 14 | |
| 229 | 241 | visible: !mon.m.disabled | |
| 230 | 242 | spacing: 4 | |
| 231 | 243 | Repeater { | |
| @@ -264,31 +276,38 @@ Flickable { | |||
| 264 | 276 | } | |
| 265 | 277 | } | |
| 266 | 278 | ||
| 267 | − | SectionLabel { text: "brightness & colour"; keywords: "backlight color" } | |
| 268 | − | Knob { | |
| 269 | − | visible: Bright.available | |
| 270 | − | label: "laptop panel" | |
| 271 | − | keywords: "brightness backlight screen" | |
| 272 | − | unit: "%" | |
| 273 | − | from: 1 | |
| 274 | − | to: 100 | |
| 275 | − | value: Bright.percent | |
| 276 | − | onMoved: x => Bright.setPercent(x) | |
| 277 | − | } | |
| 278 | − | Switch { | |
| 279 | − | visible: Toggles.hasNightLight | |
| 280 | − | label: "night light" | |
| 281 | − | keywords: "blue light warm redshift gammastep" | |
| 282 | − | hint: "gammastep's warm colours (its schedule comes from ~/.config/gammastep)" | |
| 283 | − | checked: Toggles.nightLight | |
| 284 | − | onToggled: on => Toggles.setNightLight(on) | |
| 285 | − | } | |
| 286 | − | Text { | |
| 287 | − | visible: !Toggles.hasNightLight | |
| 288 | − | text: "night light: gammastep isn't running" | |
| 289 | − | color: Theme.fgDim | |
| 290 | − | font.family: Theme.ui | |
| 291 | − | font.pixelSize: 14 | |
| 279 | + | Group { | |
| 280 | + | title: "brightness & colour" | |
| 281 | + | keywords: "backlight color" | |
| 282 | + | Knob { | |
| 283 | + | visible: Bright.available | |
| 284 | + | label: "laptop panel" | |
| 285 | + | keywords: "brightness backlight screen" | |
| 286 | + | unit: "%" | |
| 287 | + | from: 1 | |
| 288 | + | to: 100 | |
| 289 | + | value: Bright.percent | |
| 290 | + | onMoved: x => Bright.setPercent(x) | |
| 291 | + | } | |
| 292 | + | Switch { | |
| 293 | + | visible: Toggles.hasNightLight | |
| 294 | + | label: "night light" | |
| 295 | + | keywords: "blue light warm redshift gammastep" | |
| 296 | + | hint: "gammastep's warm colours (its schedule comes from ~/.config/gammastep)" | |
| 297 | + | checked: Toggles.nightLight | |
| 298 | + | onToggled: on => Toggles.setNightLight(on) | |
| 299 | + | } | |
| 300 | + | Text { | |
| 301 | + | visible: !Toggles.hasNightLight | |
| 302 | + | Layout.leftMargin: 18 | |
| 303 | + | Layout.topMargin: 12 | |
| 304 | + | Layout.bottomMargin: 12 | |
| 305 | + | text: "night light: gammastep isn't running" | |
| 306 | + | color: Theme.fgDim | |
| 307 | + | font.family: Theme.ui | |
| 308 | + | font.pixelSize: 14 | |
| 309 | + | } | |
| 292 | 310 | } | |
| 311 | + | Item { Layout.preferredHeight: 12 } | |
| 293 | 312 | } | |
| 294 | 313 | } | |
| @@ -4,34 +4,34 @@ import Quickshell | |||
| 4 | 4 | import Quickshell.Io | |
| 5 | 5 | ||
| 6 | 6 | // The settings app's sections and its search index. Like Android indexes each preference XML, the rows are parsed out | |
| 7 | − | // of the page files themselves: SectionLabel (a group) and Switch / Knob / Choice / Field / Timeout with a literal | |
| 8 | − | // `label:`, plus their `hint:`, `keywords:` (synonyms, read only here) and a Choice's option labels. IconBtn only with | |
| 9 | − | // `keywords:` (opt-in). Non-literal labels aren't indexed. `extras` covers rows without a label (wi-fi, bluetooth power). | |
| 7 | + | // of the page files themselves: Group / SectionLabel (a group: its literal `title:` / `text:`) and Switch / Knob / | |
| 8 | + | // Choice / Field / Timeout with a literal `label:`, plus their `hint:`, `keywords:` (synonyms, read only here) and a | |
| 9 | + | // Choice's option labels. IconBtn only with `keywords:` (opt-in). Non-literal labels aren't indexed. `extras` covers rows without a label (wi-fi, bluetooth power). | |
| 10 | 10 | // Editing a page hot-reloads the shell, which re-parses it. | |
| 11 | 11 | Singleton { | |
| 12 | 12 | id: idx | |
| 13 | 13 | ||
| 14 | 14 | // files: parsed in this order (a page's composite rows like BatteryInfo / GpuSwitch count as the page) | |
| 15 | 15 | readonly property var sections: [ | |
| 16 | − | { id: "network", word: "network", caption: "network · wi-fi, wired, tethering", files: ["NetPage.qml"], | |
| 16 | + | { id: "network", icon: Theme.iWifi, word: "network", caption: "network · wi-fi, wired, tethering", files: ["NetPage.qml"], | |
| 17 | 17 | keywords: "wifi internet ethernet" }, | |
| 18 | − | { id: "bluetooth", word: "bluetooth", caption: "bluetooth · devices, pairing", files: ["BtPage.qml"], keywords: "bt" }, | |
| 19 | − | { id: "sound", word: "sound", caption: "sound · levels, devices, apps", files: ["AudioPage.qml"], | |
| 18 | + | { id: "bluetooth", icon: Theme.iBt, word: "bluetooth", caption: "bluetooth · devices, pairing", files: ["BtPage.qml"], keywords: "bt" }, | |
| 19 | + | { id: "sound", icon: Theme.iVolHigh, word: "sound", caption: "sound · levels, devices, apps", files: ["AudioPage.qml"], | |
| 20 | 20 | keywords: "audio volume speakers microphone" }, | |
| 21 | − | { id: "appearance", word: "look", caption: "look · wallpaper, home screen, colours, bar", files: ["SetAppearance.qml"], | |
| 21 | + | { id: "appearance", icon: Theme.iPalette, word: "look", caption: "look · wallpaper, home screen, colours, bar", files: ["SetAppearance.qml"], | |
| 22 | 22 | keywords: "appearance theme style" }, | |
| 23 | − | { id: "shell", word: "shell", caption: "shell · home screen, dock, osd, notifications", files: ["SetShell.qml"], | |
| 23 | + | { id: "shell", icon: Theme.iDashboard, word: "shell", caption: "shell · home screen, dock, osd, notifications", files: ["SetShell.qml"], | |
| 24 | 24 | keywords: "aurora" }, | |
| 25 | − | { id: "windows", word: "windows", caption: "windows · hyprland gaps, borders, effects, layout", files: ["SetWindows.qml"], | |
| 25 | + | { id: "windows", icon: Theme.iWindows, word: "windows", caption: "windows · hyprland gaps, borders, effects, layout", files: ["SetWindows.qml"], | |
| 26 | 26 | keywords: "hyprland tiling" }, | |
| 27 | − | { id: "input", word: "input", caption: "input · keyboard, mouse, touchpad", files: ["SetInput.qml"], keywords: "" }, | |
| 28 | − | { id: "keybinds", word: "keys", caption: "keys · hyprland binds", files: [], | |
| 27 | + | { id: "input", icon: Theme.iMouse, word: "input", caption: "input · keyboard, mouse, touchpad", files: ["SetInput.qml"], keywords: "" }, | |
| 28 | + | { id: "keybinds", icon: Theme.iKeyboard, word: "keys", caption: "keys · hyprland binds", files: [], | |
| 29 | 29 | keywords: "keybinds shortcuts hotkeys bindings" }, | |
| 30 | − | { id: "display", word: "display", caption: "display · monitors, layout, night light", files: ["SetDisplay.qml"], | |
| 30 | + | { id: "display", icon: Theme.iMonitor, word: "display", caption: "display · monitors, layout, night light", files: ["SetDisplay.qml"], | |
| 31 | 31 | keywords: "screen monitor resolution" }, | |
| 32 | − | { id: "power", word: "power", caption: "power · battery, profiles, idle", files: ["BatteryInfo.qml", "SetPower.qml", "GpuSwitch.qml"], | |
| 32 | + | { id: "power", icon: Theme.iBolt, word: "power", caption: "power · battery, profiles, idle", files: ["BatteryInfo.qml", "SetPower.qml", "GpuSwitch.qml"], | |
| 33 | 33 | keywords: "energy sleep" }, | |
| 34 | − | { id: "about", word: "about", caption: "about · this system", files: ["SetAbout.qml"], keywords: "info version" } | |
| 34 | + | { id: "about", icon: Theme.iArch, word: "about", caption: "about · this system", files: ["SetAbout.qml"], keywords: "info version" } | |
| 35 | 35 | ] | |
| 36 | 36 | ||
| 37 | 37 | // rows the parser can't see. group = the SectionLabel to land on ("" = the top of the section) | |
| @@ -113,11 +113,11 @@ Singleton { | |||
| 113 | 113 | return { top: top, inner: inner } | |
| 114 | 114 | } | |
| 115 | 115 | function parse(file, t) { | |
| 116 | − | var out = [], group = "", groupKw = "", re = /\b(SectionLabel|Switch|Knob|Choice|Field|Timeout|IconBtn)\s*\{/g, m | |
| 116 | + | var out = [], group = "", groupKw = "", re = /\b(Group|SectionLabel|Switch|Knob|Choice|Field|Timeout|IconBtn)\s*\{/g, m | |
| 117 | 117 | while ((m = re.exec(t)) !== null) { | |
| 118 | 118 | var b = block(t, re.lastIndex), kw = lit(b.top, "keywords") | |
| 119 | − | if (m[1] === "SectionLabel") { | |
| 120 | − | group = lit(b.top, "text") || "" | |
| 119 | + | if (m[1] === "Group" || m[1] === "SectionLabel") { | |
| 120 | + | group = lit(b.top, m[1] === "Group" ? "title" : "text") || "" | |
| 121 | 121 | groupKw = group !== "" ? kw || "" : "" | |
| 122 | 122 | if (group !== "") out.push({ kind: "group", group: group, label: group, hint: "", keywords: groupKw, context: "" }) | |
| 123 | 123 | continue | |
| @@ -15,7 +15,7 @@ Flickable { | |||
| 15 | 15 | ColumnLayout { | |
| 16 | 16 | id: col | |
| 17 | 17 | width: flick.width | |
| 18 | − | spacing: 8 | |
| 18 | + | spacing: 22 | |
| 19 | 19 | ||
| 20 | 20 | Text { | |
| 21 | 21 | Layout.fillWidth: true | |
| @@ -28,47 +28,61 @@ Flickable { | |||
| 28 | 28 | font.pixelSize: 14 | |
| 29 | 29 | } | |
| 30 | 30 | ||
| 31 | − | SectionLabel { text: "keyboard"; keywords: "xkb typing" } | |
| 32 | − | Field { label: "layouts"; keywords: "keyboard layout language kb_layout"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) } | |
| 33 | − | Field { label: "variants"; keywords: "keyboard variant dvorak colemak"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) } | |
| 34 | − | Field { label: "xkb options"; keywords: "caps lock compose kb_options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) } | |
| 35 | − | Knob { label: "repeat rate"; keywords: "key repeat speed"; unit: "/s"; from: 10; to: 100; value: flick.v("input:repeat_rate", 25); onMoved: x => HyprConf.set("input:repeat_rate", x) } | |
| 36 | − | Knob { label: "repeat delay"; keywords: "key repeat"; unit: "ms"; from: 150; to: 1000; value: flick.v("input:repeat_delay", 600); onMoved: x => HyprConf.set("input:repeat_delay", Math.round(x / 10) * 10) } | |
| 37 | − | ||
| 38 | − | SectionLabel { text: "mouse"; keywords: "pointer" } | |
| 39 | − | Knob { label: "sensitivity"; keywords: "mouse speed pointer"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) } | |
| 40 | − | Choice { | |
| 41 | − | label: "acceleration" | |
| 42 | − | keywords: "mouse accel" | |
| 43 | − | options: [{ value: "", label: "default" }, { value: "flat", label: "flat" }, { value: "adaptive", label: "adaptive" }] | |
| 44 | − | value: flick.v("input:accel_profile", "") | |
| 45 | − | onPicked: x => HyprConf.set("input:accel_profile", x) | |
| 31 | + | Group { | |
| 32 | + | title: "keyboard" | |
| 33 | + | keywords: "xkb typing" | |
| 34 | + | Field { label: "layouts"; keywords: "keyboard layout language kb_layout"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) } | |
| 35 | + | Field { label: "variants"; keywords: "keyboard variant dvorak colemak"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) } | |
| 36 | + | Field { label: "xkb options"; keywords: "caps lock compose kb_options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) } | |
| 37 | + | Knob { label: "repeat rate"; keywords: "key repeat speed"; unit: "/s"; from: 10; to: 100; value: flick.v("input:repeat_rate", 25); onMoved: x => HyprConf.set("input:repeat_rate", x) } | |
| 38 | + | Knob { label: "repeat delay"; keywords: "key repeat"; unit: "ms"; from: 150; to: 1000; value: flick.v("input:repeat_delay", 600); onMoved: x => HyprConf.set("input:repeat_delay", Math.round(x / 10) * 10) } | |
| 46 | 39 | } | |
| 47 | − | Choice { | |
| 48 | − | label: "focus follows mouse" | |
| 49 | − | keywords: "focus hover" | |
| 50 | − | options: [{ value: 0, label: "off" }, { value: 1, label: "always" }, { value: 2, label: "loose" }, { value: 3, label: "hover only" }] | |
| 51 | − | value: flick.v("input:follow_mouse", -1) | |
| 52 | − | onPicked: x => HyprConf.set("input:follow_mouse", x) | |
| 40 | + | ||
| 41 | + | Group { | |
| 42 | + | title: "mouse" | |
| 43 | + | keywords: "pointer" | |
| 44 | + | Knob { label: "sensitivity"; keywords: "mouse speed pointer"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) } | |
| 45 | + | Choice { | |
| 46 | + | label: "acceleration" | |
| 47 | + | keywords: "mouse accel" | |
| 48 | + | options: [{ value: "", label: "default" }, { value: "flat", label: "flat" }, { value: "adaptive", label: "adaptive" }] | |
| 49 | + | value: flick.v("input:accel_profile", "") | |
| 50 | + | onPicked: x => HyprConf.set("input:accel_profile", x) | |
| 51 | + | } | |
| 52 | + | Choice { | |
| 53 | + | label: "focus follows mouse" | |
| 54 | + | keywords: "focus hover" | |
| 55 | + | options: [{ value: 0, label: "off" }, { value: 1, label: "always" }, { value: 2, label: "loose" }, { value: 3, label: "hover only" }] | |
| 56 | + | value: flick.v("input:follow_mouse", -1) | |
| 57 | + | onPicked: x => HyprConf.set("input:follow_mouse", x) | |
| 58 | + | } | |
| 59 | + | Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) } | |
| 60 | + | Switch { label: "left handed"; keywords: "swap buttons"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) } | |
| 53 | 61 | } | |
| 54 | − | Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) } | |
| 55 | − | Switch { label: "left handed"; keywords: "swap buttons"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) } | |
| 56 | 62 | ||
| 57 | − | SectionLabel { text: "touchpad"; keywords: "trackpad" } | |
| 58 | − | Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) } | |
| 59 | − | Switch { label: "tap to click"; keywords: "tap"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) } | |
| 60 | − | Switch { label: "disable while typing"; keywords: "palm rejection dwt"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) } | |
| 61 | − | Knob { label: "scroll speed"; keywords: "scroll factor"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) } | |
| 63 | + | Group { | |
| 64 | + | title: "touchpad" | |
| 65 | + | keywords: "trackpad" | |
| 66 | + | Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) } | |
| 67 | + | Switch { label: "tap to click"; keywords: "tap"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) } | |
| 68 | + | Switch { label: "disable while typing"; keywords: "palm rejection dwt"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) } | |
| 69 | + | Knob { label: "scroll speed"; keywords: "scroll factor"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) } | |
| 70 | + | } | |
| 62 | 71 | ||
| 63 | − | SectionLabel { text: "gestures"; keywords: "swipe touchpad fingers" } | |
| 64 | − | Switch { label: "invert swipe"; hint: "reverse the 3-finger workspace swipe"; checked: flick.v("gestures:workspace_swipe_invert", true); onToggled: on => HyprConf.set("gestures:workspace_swipe_invert", on) } | |
| 65 | − | Knob { label: "swipe distance"; unit: "px"; from: 100; to: 1000; value: flick.v("gestures:workspace_swipe_distance", 300); onMoved: x => HyprConf.set("gestures:workspace_swipe_distance", Math.round(x / 10) * 10) } | |
| 72 | + | Group { | |
| 73 | + | title: "gestures" | |
| 74 | + | keywords: "swipe touchpad fingers" | |
| 75 | + | Switch { label: "invert swipe"; hint: "reverse the 3-finger workspace swipe"; checked: flick.v("gestures:workspace_swipe_invert", true); onToggled: on => HyprConf.set("gestures:workspace_swipe_invert", on) } | |
| 76 | + | Knob { label: "swipe distance"; unit: "px"; from: 100; to: 1000; value: flick.v("gestures:workspace_swipe_distance", 300); onMoved: x => HyprConf.set("gestures:workspace_swipe_distance", Math.round(x / 10) * 10) } | |
| 77 | + | } | |
| 66 | 78 | ||
| 67 | − | SectionLabel { text: "cursor"; keywords: "mouse pointer" } | |
| 68 | − | Knob { label: "hide when idle"; keywords: "cursor inactive timeout"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) } | |
| 69 | − | Switch { label: "hide while typing"; keywords: "cursor"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) } | |
| 79 | + | Group { | |
| 80 | + | title: "cursor" | |
| 81 | + | keywords: "mouse pointer" | |
| 82 | + | Knob { label: "hide when idle"; keywords: "cursor inactive timeout"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) } | |
| 83 | + | Switch { label: "hide while typing"; keywords: "cursor"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) } | |
| 84 | + | } | |
| 70 | 85 | ||
| 71 | − | Item { Layout.preferredHeight: 6 } | |
| 72 | 86 | RowLayout { | |
| 73 | 87 | Layout.fillWidth: true | |
| 74 | 88 | Text { | |
| @@ -82,5 +96,6 @@ Flickable { | |||
| 82 | 96 | } | |
| 83 | 97 | IconBtn { icon: Theme.iRefresh; label: "reset to config"; keywords: "reset defaults restore hyprland"; onClicked: HyprConf.resetAll(["input:", "cursor:", "gestures:"]) } | |
| 84 | 98 | } | |
| 99 | + | Item { Layout.preferredHeight: 12 } | |
| 85 | 100 | } | |
| 86 | 101 | } | |
| @@ -7,7 +7,7 @@ import Quickshell.Io | |||
| 7 | 7 | // (`hyprctl binds` only shows "__lua <n>" for Lua binds). Comments above binds become the groups. | |
| 8 | 8 | ColumnLayout { | |
| 9 | 9 | id: page | |
| 10 | − | spacing: 8 | |
| 10 | + | spacing: 16 | |
| 11 | 11 | readonly property string file: Quickshell.env("HOME") + "/.config/hypr/source/keybinds.lua" | |
| 12 | 12 | property var binds: [] // [{group, keys, action}] | |
| 13 | 13 | property string query: "" | |
| @@ -99,49 +99,71 @@ ColumnLayout { | |||
| 99 | 99 | Layout.fillWidth: true | |
| 100 | 100 | Layout.fillHeight: true | |
| 101 | 101 | clip: true | |
| 102 | − | spacing: 2 | |
| 102 | + | spacing: 0 | |
| 103 | 103 | model: page.shown | |
| 104 | 104 | boundsBehavior: Flickable.StopAtBounds | |
| 105 | − | delegate: RowLayout { | |
| 105 | + | // a group header, or a bind on its group's card: s1 rows whose outer corners round where the group starts / ends | |
| 106 | + | delegate: Item { | |
| 107 | + | id: d | |
| 106 | 108 | required property var modelData | |
| 109 | + | required property int index | |
| 107 | 110 | readonly property bool isHeader: modelData.header !== undefined | |
| 111 | + | readonly property bool first: index === 0 || page.shown[index - 1].header !== undefined | |
| 112 | + | readonly property bool last: index === page.shown.length - 1 || page.shown[index + 1].header !== undefined | |
| 108 | 113 | width: ListView.view.width | |
| 109 | − | spacing: 10 | |
| 114 | + | height: isHeader ? (index === 0 ? 0 : 22) + 30 : 44 + (first ? 8 : 0) + (last ? 8 : 0) | |
| 110 | 115 | SectionLabel { | |
| 111 | − | visible: parent.isHeader | |
| 112 | − | Layout.topMargin: 6 | |
| 113 | − | text: parent.isHeader ? parent.modelData.header : "" | |
| 116 | + | visible: d.isHeader | |
| 117 | + | anchors.left: parent.left | |
| 118 | + | anchors.leftMargin: 14 | |
| 119 | + | anchors.right: parent.right | |
| 120 | + | anchors.bottom: parent.bottom | |
| 121 | + | anchors.bottomMargin: 10 | |
| 122 | + | text: d.isHeader ? d.modelData.header : "" | |
| 114 | 123 | } | |
| 115 | 124 | Rectangle { | |
| 116 | − | visible: !parent.isHeader | |
| 117 | − | Layout.preferredWidth: 240 | |
| 118 | − | Layout.leftMargin: 18 | |
| 119 | − | implicitHeight: 30 | |
| 120 | − | radius: 15 | |
| 121 | − | color: Theme.s2 | |
| 122 | − | Lb { | |
| 123 | − | anchors.fill: parent | |
| 124 | − | anchors.leftMargin: 14 | |
| 125 | − | anchors.rightMargin: 10 | |
| 126 | − | verticalAlignment: Text.AlignVCenter | |
| 127 | − | text: modelData.keys || "" | |
| 128 | − | color: Theme.fill | |
| 129 | − | elide: Text.ElideRight | |
| 130 | − | px: 10 | |
| 125 | + | visible: !d.isHeader | |
| 126 | + | anchors.fill: parent | |
| 127 | + | color: Theme.s1 | |
| 128 | + | topLeftRadius: d.first ? 36 : 0 | |
| 129 | + | topRightRadius: d.first ? 36 : 0 | |
| 130 | + | bottomLeftRadius: d.last ? 36 : 0 | |
| 131 | + | bottomRightRadius: d.last ? 36 : 0 | |
| 132 | + | RowLayout { | |
| 133 | + | x: 26 | |
| 134 | + | y: (d.first ? 8 : 0) + (44 - height) / 2 | |
| 135 | + | width: parent.width - 52 | |
| 136 | + | spacing: 10 | |
| 137 | + | Rectangle { | |
| 138 | + | Layout.preferredWidth: 240 | |
| 139 | + | implicitHeight: 30 | |
| 140 | + | radius: 15 | |
| 141 | + | color: Theme.s2 | |
| 142 | + | Lb { | |
| 143 | + | anchors.fill: parent | |
| 144 | + | anchors.leftMargin: 14 | |
| 145 | + | anchors.rightMargin: 10 | |
| 146 | + | verticalAlignment: Text.AlignVCenter | |
| 147 | + | text: d.modelData.keys || "" | |
| 148 | + | color: Theme.fill | |
| 149 | + | elide: Text.ElideRight | |
| 150 | + | px: 10 | |
| 151 | + | } | |
| 152 | + | } | |
| 153 | + | Text { | |
| 154 | + | Layout.fillWidth: true | |
| 155 | + | text: d.modelData.action || "" | |
| 156 | + | color: Theme.fg | |
| 157 | + | elide: Text.ElideRight | |
| 158 | + | font.family: Theme.ui | |
| 159 | + | font.pixelSize: 14 | |
| 160 | + | } | |
| 131 | 161 | } | |
| 132 | 162 | } | |
| 133 | − | Text { | |
| 134 | − | visible: !parent.isHeader | |
| 135 | − | Layout.fillWidth: true | |
| 136 | − | text: modelData.action || "" | |
| 137 | − | color: Theme.fg | |
| 138 | − | elide: Text.ElideRight | |
| 139 | − | font.family: Theme.ui | |
| 140 | − | font.pixelSize: 14 | |
| 141 | − | } | |
| 142 | 163 | } | |
| 143 | 164 | } | |
| 144 | 165 | Text { | |
| 166 | + | Layout.leftMargin: 18 | |
| 145 | 167 | text: page.binds.length + " binds · " + page.file.replace(Quickshell.env("HOME"), "~") | |
| 146 | 168 | color: Theme.fgDim | |
| 147 | 169 | font.family: Theme.ui | |
| @@ -65,95 +65,110 @@ Flickable { | |||
| 65 | 65 | ColumnLayout { | |
| 66 | 66 | id: col | |
| 67 | 67 | width: flick.width | |
| 68 | − | spacing: 8 | |
| 68 | + | spacing: 22 | |
| 69 | 69 | ||
| 70 | − | BatteryInfo {} | |
| 70 | + | BatteryInfo { cards: true } | |
| 71 | 71 | ||
| 72 | − | SectionLabel { text: "battery care"; keywords: "battery health"; Layout.topMargin: 6; visible: Battery.hasBat } | |
| 73 | − | Choice { | |
| 74 | − | visible: Battery.supported | |
| 75 | − | label: "charge limit" | |
| 76 | − | keywords: "conservation 80% battery health longevity" | |
| 77 | − | options: [{ value: "off", label: "off" }, { value: "conservation", label: "conservation" }] | |
| 78 | − | value: Settings.chargeMode === "off" ? "off" : "conservation" | |
| 79 | − | onPicked: v => Settings.chargeMode = v | |
| 80 | − | } | |
| 81 | − | Text { | |
| 82 | − | visible: Battery.supported | |
| 83 | − | Layout.fillWidth: true | |
| 84 | − | wrapMode: Text.WordWrap | |
| 85 | − | text: Settings.chargeMode !== "off" | |
| 86 | − | ? "the firmware stops charging at ~80%, even when aurora isn't running. Conservation mode is " | |
| 87 | − | + (Battery.capOn ? "on" : "off") + "." | |
| 88 | − | : "charges to 100%" | |
| 89 | − | color: Theme.fgDim | |
| 90 | − | font.family: Theme.ui | |
| 91 | − | font.pixelSize: 13 | |
| 92 | − | } | |
| 93 | − | Knob { | |
| 72 | + | Group { | |
| 73 | + | title: "battery care" | |
| 74 | + | keywords: "battery health" | |
| 94 | 75 | visible: Battery.hasBat | |
| 95 | − | label: "warn at" | |
| 96 | − | keywords: "low battery warning" | |
| 97 | − | from: 5; to: 40; unit: "%" | |
| 98 | − | value: Settings.batLow | |
| 99 | − | onMoved: v => Settings.batLow = v | |
| 100 | − | } | |
| 101 | − | Knob { | |
| 102 | − | visible: Battery.hasBat | |
| 103 | − | label: "critical at" | |
| 104 | − | keywords: "critical battery" | |
| 105 | − | from: 2; to: 15; unit: "%" | |
| 106 | − | value: Settings.batCritical | |
| 107 | − | onMoved: v => Settings.batCritical = v | |
| 108 | − | } | |
| 109 | − | Choice { | |
| 110 | − | visible: Battery.hasBat | |
| 111 | − | label: "when critical" | |
| 112 | − | keywords: "low battery action" | |
| 113 | − | options: [{ value: "none", label: "warn" }, { value: "suspend", label: "suspend" }, | |
| 114 | − | { value: "poweroff", label: "power off" }] | |
| 115 | − | value: Settings.batCriticalAction | |
| 116 | − | onPicked: v => Settings.batCriticalAction = v | |
| 76 | + | Choice { | |
| 77 | + | visible: Battery.supported | |
| 78 | + | label: "charge limit" | |
| 79 | + | keywords: "conservation 80% battery health longevity" | |
| 80 | + | options: [{ value: "off", label: "off" }, { value: "conservation", label: "conservation" }] | |
| 81 | + | value: Settings.chargeMode === "off" ? "off" : "conservation" | |
| 82 | + | onPicked: v => Settings.chargeMode = v | |
| 83 | + | } | |
| 84 | + | Text { | |
| 85 | + | visible: Battery.supported | |
| 86 | + | Layout.fillWidth: true | |
| 87 | + | Layout.leftMargin: 18 | |
| 88 | + | Layout.rightMargin: 18 | |
| 89 | + | Layout.bottomMargin: 6 | |
| 90 | + | wrapMode: Text.WordWrap | |
| 91 | + | text: Settings.chargeMode !== "off" | |
| 92 | + | ? "the firmware stops charging at ~80%, even when aurora isn't running. Conservation mode is " | |
| 93 | + | + (Battery.capOn ? "on" : "off") + "." | |
| 94 | + | : "charges to 100%" | |
| 95 | + | color: Theme.fgDim | |
| 96 | + | font.family: Theme.ui | |
| 97 | + | font.pixelSize: 13 | |
| 98 | + | } | |
| 99 | + | Knob { | |
| 100 | + | visible: Battery.hasBat | |
| 101 | + | label: "warn at" | |
| 102 | + | keywords: "low battery warning" | |
| 103 | + | from: 5; to: 40; unit: "%" | |
| 104 | + | value: Settings.batLow | |
| 105 | + | onMoved: v => Settings.batLow = v | |
| 106 | + | } | |
| 107 | + | Knob { | |
| 108 | + | visible: Battery.hasBat | |
| 109 | + | label: "critical at" | |
| 110 | + | keywords: "critical battery" | |
| 111 | + | from: 2; to: 15; unit: "%" | |
| 112 | + | value: Settings.batCritical | |
| 113 | + | onMoved: v => Settings.batCritical = v | |
| 114 | + | } | |
| 115 | + | Choice { | |
| 116 | + | visible: Battery.hasBat | |
| 117 | + | label: "when critical" | |
| 118 | + | keywords: "low battery action" | |
| 119 | + | options: [{ value: "none", label: "warn" }, { value: "suspend", label: "suspend" }, | |
| 120 | + | { value: "poweroff", label: "power off" }] | |
| 121 | + | value: Settings.batCriticalAction | |
| 122 | + | onPicked: v => Settings.batCriticalAction = v | |
| 123 | + | } | |
| 117 | 124 | } | |
| 118 | 125 | ||
| 119 | − | Choice { | |
| 120 | − | Layout.topMargin: 6 | |
| 121 | − | visible: Battery.hasProfiles | |
| 122 | − | label: "power profile" | |
| 126 | + | Group { | |
| 127 | + | title: "power profile" | |
| 123 | 128 | keywords: "performance balanced saver power-profiles-daemon" | |
| 124 | − | options: flick.profiles.concat(Battery.hasBat ? [{ value: "auto", label: "auto" }] : []) | |
| 125 | − | value: Battery.profile | |
| 126 | − | onPicked: v => Battery.setProfile(v) | |
| 127 | − | } | |
| 128 | − | Choice { | |
| 129 | − | visible: Battery.hasProfiles && Settings.powerAuto | |
| 130 | − | label: "on AC" | |
| 131 | − | keywords: "plugged in charger power profile" | |
| 132 | − | options: flick.profiles | |
| 133 | − | value: Settings.powerAc | |
| 134 | − | onPicked: v => Settings.powerAc = v | |
| 135 | − | } | |
| 136 | − | Choice { | |
| 137 | − | visible: Battery.hasProfiles && Settings.powerAuto | |
| 138 | − | label: "on battery" | |
| 139 | − | keywords: "unplugged power profile" | |
| 140 | − | options: flick.profiles | |
| 141 | − | value: Settings.powerBat | |
| 142 | − | onPicked: v => Settings.powerBat = v | |
| 129 | + | visible: Battery.hasProfiles | |
| 130 | + | Choice { | |
| 131 | + | visible: Battery.hasProfiles | |
| 132 | + | label: "power profile" | |
| 133 | + | keywords: "performance balanced saver power-profiles-daemon" | |
| 134 | + | options: flick.profiles.concat(Battery.hasBat ? [{ value: "auto", label: "auto" }] : []) | |
| 135 | + | value: Battery.profile | |
| 136 | + | onPicked: v => Battery.setProfile(v) | |
| 137 | + | } | |
| 138 | + | Choice { | |
| 139 | + | visible: Battery.hasProfiles && Settings.powerAuto | |
| 140 | + | label: "on AC" | |
| 141 | + | keywords: "plugged in charger power profile" | |
| 142 | + | options: flick.profiles | |
| 143 | + | value: Settings.powerAc | |
| 144 | + | onPicked: v => Settings.powerAc = v | |
| 145 | + | } | |
| 146 | + | Choice { | |
| 147 | + | visible: Battery.hasProfiles && Settings.powerAuto | |
| 148 | + | label: "on battery" | |
| 149 | + | keywords: "unplugged power profile" | |
| 150 | + | options: flick.profiles | |
| 151 | + | value: Settings.powerBat | |
| 152 | + | onPicked: v => Settings.powerBat = v | |
| 153 | + | } | |
| 143 | 154 | } | |
| 144 | − | GpuSwitch { Layout.topMargin: 6 } | |
| 145 | 155 | ||
| 146 | − | SectionLabel { text: "when idle"; keywords: "idle timeout sleep hypridle"; Layout.topMargin: 6 } | |
| 147 | − | Timeout { label: "dim the screen"; keywords: "idle dim timeout"; key: "idleDim"; value: Idle.dim } | |
| 148 | − | Timeout { label: "screen off"; keywords: "dpms display off idle timeout"; key: "idleOff"; value: Idle.screenOff } | |
| 149 | − | Timeout { label: "lock"; keywords: "lock screen hyprlock idle timeout"; key: "idleLock"; value: Idle.lock } | |
| 150 | − | Timeout { label: "suspend"; keywords: "sleep idle timeout"; key: "idleSuspend"; value: Idle.suspend; max: 3600 } | |
| 151 | − | Switch { | |
| 152 | − | label: "caffeine" | |
| 153 | − | keywords: "stay awake keep awake inhibit idle" | |
| 154 | − | hint: "keep the screen awake until turned off (not saved)" | |
| 155 | − | checked: Toggles.caffeine | |
| 156 | − | onToggled: on => Toggles.caffeine = on | |
| 156 | + | GpuSwitch { cards: true } | |
| 157 | + | ||
| 158 | + | Group { | |
| 159 | + | title: "when idle" | |
| 160 | + | keywords: "idle timeout sleep hypridle" | |
| 161 | + | Timeout { label: "dim the screen"; keywords: "idle dim timeout"; key: "idleDim"; value: Idle.dim } | |
| 162 | + | Timeout { label: "screen off"; keywords: "dpms display off idle timeout"; key: "idleOff"; value: Idle.screenOff } | |
| 163 | + | Timeout { label: "lock"; keywords: "lock screen hyprlock idle timeout"; key: "idleLock"; value: Idle.lock } | |
| 164 | + | Timeout { label: "suspend"; keywords: "sleep idle timeout"; key: "idleSuspend"; value: Idle.suspend; max: 3600 } | |
| 165 | + | Switch { | |
| 166 | + | label: "caffeine" | |
| 167 | + | keywords: "stay awake keep awake inhibit idle" | |
| 168 | + | hint: "keep the screen awake until turned off (not saved)" | |
| 169 | + | checked: Toggles.caffeine | |
| 170 | + | onToggled: on => Toggles.caffeine = on | |
| 171 | + | } | |
| 157 | 172 | } | |
| 158 | 173 | Text { | |
| 159 | 174 | Layout.fillWidth: true | |
| @@ -164,5 +179,6 @@ Flickable { | |||
| 164 | 179 | font.family: Theme.ui | |
| 165 | 180 | font.pixelSize: 13 | |
| 166 | 181 | } | |
| 182 | + | Item { Layout.preferredHeight: 12 } | |
| 167 | 183 | } | |
| 168 | 184 | } | |
| @@ -12,76 +12,100 @@ Flickable { | |||
| 12 | 12 | ColumnLayout { | |
| 13 | 13 | id: col | |
| 14 | 14 | width: flick.width | |
| 15 | − | spacing: 8 | |
| 15 | + | spacing: 22 | |
| 16 | 16 | ||
| 17 | − | SectionLabel { text: "desktop"; keywords: "home screen widgets" } | |
| 18 | − | Switch { label: "clock"; keywords: "desktop clock widget date"; hint: "Doto clock, date, stats and what's playing on the desktop"; checked: Settings.desktopClock; onToggled: on => Settings.desktopClock = on } | |
| 19 | − | Switch { label: "visualizer"; keywords: "cava spectrum music audio"; hint: "cava as columns of dots along the bottom while music plays"; checked: Settings.desktopViz; onToggled: on => Settings.desktopViz = on } | |
| 20 | − | Knob { label: "visualizer bars"; keywords: "cava spectrum"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v } | |
| 21 | − | ||
| 22 | − | SectionLabel { text: "dock"; keywords: "taskbar launcher pinned apps" } | |
| 23 | − | Switch { label: "dock"; keywords: "taskbar launcher"; 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.fgDim | |
| 27 | − | font.family: Theme.ui | |
| 28 | − | font.pixelSize: 14 | |
| 17 | + | Group { | |
| 18 | + | title: "desktop" | |
| 19 | + | keywords: "home screen widgets" | |
| 20 | + | Switch { label: "clock"; keywords: "desktop clock widget date"; hint: "Doto clock, date, stats and what's playing on the desktop"; checked: Settings.desktopClock; onToggled: on => Settings.desktopClock = on } | |
| 21 | + | Switch { label: "visualizer"; keywords: "cava spectrum music audio"; hint: "cava as columns of dots along the bottom while music plays"; checked: Settings.desktopViz; onToggled: on => Settings.desktopViz = on } | |
| 22 | + | Knob { label: "visualizer bars"; keywords: "cava spectrum"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v } | |
| 29 | 23 | } | |
| 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) | |
| 24 | + | ||
| 25 | + | Group { | |
| 26 | + | title: "dock" | |
| 27 | + | keywords: "taskbar launcher pinned apps" | |
| 28 | + | Switch { label: "dock"; keywords: "taskbar launcher"; hint: "autohides at the bottom edge"; checked: Settings.dock; onToggled: on => Settings.dock = on } | |
| 29 | + | Text { | |
| 30 | + | Layout.leftMargin: 18 | |
| 31 | + | Layout.topMargin: 4 | |
| 32 | + | text: "pinned apps (pin more from the dock's right-click)" | |
| 33 | + | color: Theme.fgDim | |
| 34 | + | font.family: Theme.ui | |
| 35 | + | font.pixelSize: 14 | |
| 36 | + | } | |
| 37 | + | Flow { | |
| 38 | + | Layout.fillWidth: true | |
| 39 | + | Layout.leftMargin: 14 | |
| 40 | + | Layout.rightMargin: 14 | |
| 41 | + | Layout.bottomMargin: 8 | |
| 42 | + | spacing: 4 | |
| 43 | + | Repeater { | |
| 44 | + | model: Settings.dockPinned | |
| 45 | + | delegate: IconBtn { | |
| 46 | + | required property string modelData | |
| 47 | + | implicitHeight: 26 | |
| 48 | + | icon: Theme.iClose | |
| 49 | + | iconSize: 11 | |
| 50 | + | label: modelData | |
| 51 | + | onClicked: Settings.togglePin(modelData) | |
| 52 | + | } | |
| 42 | 53 | } | |
| 43 | 54 | } | |
| 44 | 55 | } | |
| 45 | 56 | ||
| 46 | − | SectionLabel { text: "on-screen display"; keywords: "osd popup indicator" } | |
| 47 | − | Knob { label: "osd timeout"; keywords: "on-screen display duration volume brightness popup"; unit: "ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 } | |
| 48 | − | ||
| 49 | − | SectionLabel { text: "network" } | |
| 50 | − | Switch { label: "captive portals"; keywords: "hotel campus wifi login sign in hotspot"; hint: "open the login page in the browser when a network needs one"; checked: Settings.portalAutoOpen; onToggled: on => Settings.portalAutoOpen = on } | |
| 57 | + | Group { | |
| 58 | + | title: "on-screen display" | |
| 59 | + | keywords: "osd popup indicator" | |
| 60 | + | Knob { label: "osd timeout"; keywords: "on-screen display duration volume brightness popup"; unit: "ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 } | |
| 61 | + | } | |
| 51 | 62 | ||
| 52 | − | SectionLabel { text: "bar" } | |
| 53 | − | Switch { label: "privacy indicators"; keywords: "microphone mic camera webcam screen sharing recording"; hint: "show in the bar when an app records the mic, uses the camera or shares the screen"; checked: Settings.privacyIndicators; onToggled: on => Settings.privacyIndicators = on } | |
| 63 | + | Group { | |
| 64 | + | title: "network" | |
| 65 | + | Switch { label: "captive portals"; keywords: "hotel campus wifi login sign in hotspot"; hint: "open the login page in the browser when a network needs one"; checked: Settings.portalAutoOpen; onToggled: on => Settings.portalAutoOpen = on } | |
| 66 | + | } | |
| 54 | 67 | ||
| 55 | − | SectionLabel { text: "weather"; keywords: "forecast temperature" } | |
| 56 | − | Field { | |
| 57 | − | label: "location" | |
| 58 | − | keywords: "city place weather" | |
| 59 | − | placeholder: "by IP · or a city · or lat,lon" | |
| 60 | − | value: Settings.weatherLoc | |
| 61 | − | onSubmitted: v => Settings.weatherLoc = v.trim() | |
| 68 | + | Group { | |
| 69 | + | title: "bar" | |
| 70 | + | Switch { label: "privacy indicators"; keywords: "microphone mic camera webcam screen sharing recording"; hint: "show in the bar when an app records the mic, uses the camera or shares the screen"; checked: Settings.privacyIndicators; onToggled: on => Settings.privacyIndicators = on } | |
| 62 | 71 | } | |
| 63 | − | Text { | |
| 64 | − | Layout.fillWidth: true | |
| 65 | − | Layout.leftMargin: 18 | |
| 66 | − | text: Weather.ok ? (Weather.place !== "" ? Weather.place + ": " : "") + Math.round(Weather.now.temp) + "°, " + Weather.name(Weather.now.code) | |
| 67 | − | + (Weather.error !== "" ? " (" + Weather.error + ")" : "") | |
| 68 | − | : Weather.busy ? "looking outside..." : Weather.error || "no weather yet" | |
| 69 | − | color: Weather.error !== "" ? Theme.err : Theme.fgDim | |
| 70 | − | font.family: Theme.ui | |
| 71 | − | font.pixelSize: 13 | |
| 72 | + | ||
| 73 | + | Group { | |
| 74 | + | title: "weather" | |
| 75 | + | keywords: "forecast temperature" | |
| 76 | + | Field { | |
| 77 | + | label: "location" | |
| 78 | + | keywords: "city place weather" | |
| 79 | + | placeholder: "by IP · or a city · or lat,lon" | |
| 80 | + | value: Settings.weatherLoc | |
| 81 | + | onSubmitted: v => Settings.weatherLoc = v.trim() | |
| 82 | + | } | |
| 83 | + | Text { | |
| 84 | + | Layout.fillWidth: true | |
| 85 | + | Layout.leftMargin: 18 | |
| 86 | + | Layout.bottomMargin: 8 | |
| 87 | + | text: Weather.ok ? (Weather.place !== "" ? Weather.place + ": " : "") + Math.round(Weather.now.temp) + "°, " + Weather.name(Weather.now.code) | |
| 88 | + | + (Weather.error !== "" ? " (" + Weather.error + ")" : "") | |
| 89 | + | : Weather.busy ? "looking outside..." : Weather.error || "no weather yet" | |
| 90 | + | color: Weather.error !== "" ? Theme.err : Theme.fgDim | |
| 91 | + | font.family: Theme.ui | |
| 92 | + | font.pixelSize: 13 | |
| 93 | + | } | |
| 72 | 94 | } | |
| 73 | 95 | ||
| 74 | − | SectionLabel { text: "notifications"; keywords: "popups toasts alerts" } | |
| 75 | − | Switch { label: "do not disturb"; keywords: "dnd silence quiet focus"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on } | |
| 76 | − | Switch { label: "sound"; keywords: "notification chime"; checked: Settings.notifSound; onToggled: on => Settings.notifSound = on } | |
| 77 | − | RowLayout { | |
| 78 | − | Layout.fillWidth: true | |
| 79 | − | spacing: 8 | |
| 80 | − | Knob { label: "sound volume"; keywords: "notification chime loudness"; from: 0; to: 1; decimals: 2; value: Settings.notifVolume; onMoved: v => Settings.notifVolume = v } | |
| 81 | − | IconBtn { icon: Theme.iPlay; implicitHeight: 26; onClicked: Notifs.playSound() } | |
| 96 | + | Group { | |
| 97 | + | title: "notifications" | |
| 98 | + | keywords: "popups toasts alerts" | |
| 99 | + | Switch { label: "do not disturb"; keywords: "dnd silence quiet focus"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on } | |
| 100 | + | Switch { label: "sound"; keywords: "notification chime"; checked: Settings.notifSound; onToggled: on => Settings.notifSound = on } | |
| 101 | + | RowLayout { | |
| 102 | + | Layout.fillWidth: true | |
| 103 | + | spacing: 8 | |
| 104 | + | Knob { label: "sound volume"; keywords: "notification chime loudness"; from: 0; to: 1; decimals: 2; value: Settings.notifVolume; onMoved: v => Settings.notifVolume = v } | |
| 105 | + | IconBtn { Layout.rightMargin: 14; icon: Theme.iPlay; implicitHeight: 26; onClicked: Notifs.playSound() } | |
| 106 | + | } | |
| 82 | 107 | } | |
| 83 | 108 | ||
| 84 | − | Item { Layout.preferredHeight: 6 } | |
| 85 | 109 | RowLayout { | |
| 86 | 110 | Layout.fillWidth: true | |
| 87 | 111 | Text { | |
| @@ -95,5 +119,6 @@ Flickable { | |||
| 95 | 119 | } | |
| 96 | 120 | IconBtn { icon: Theme.iRefresh; label: "reset shell settings"; keywords: "reset defaults restore"; onClicked: Settings.reset() } | |
| 97 | 121 | } | |
| 122 | + | Item { Layout.preferredHeight: 12 } | |
| 98 | 123 | } | |
| 99 | 124 | } | |
| @@ -16,7 +16,7 @@ Flickable { | |||
| 16 | 16 | ColumnLayout { | |
| 17 | 17 | id: col | |
| 18 | 18 | width: flick.width | |
| 19 | − | spacing: 8 | |
| 19 | + | spacing: 22 | |
| 20 | 20 | ||
| 21 | 21 | Text { | |
| 22 | 22 | Layout.fillWidth: true | |
| @@ -29,75 +29,86 @@ Flickable { | |||
| 29 | 29 | font.pixelSize: 14 | |
| 30 | 30 | } | |
| 31 | 31 | ||
| 32 | − | SectionLabel { text: "gaps & borders"; keywords: "spacing margins" } | |
| 33 | − | Knob { label: "inner gaps"; keywords: "spacing padding gaps_in"; unit: "px"; from: 0; to: 30; value: flick.v("general:gaps_in", 0); onMoved: x => HyprConf.set("general:gaps_in", x) } | |
| 34 | − | Knob { label: "outer gaps"; keywords: "spacing margin gaps_out"; unit: "px"; from: 0; to: 40; value: flick.v("general:gaps_out", 0); onMoved: x => HyprConf.set("general:gaps_out", x) } | |
| 35 | − | Knob { label: "border"; keywords: "border size thickness outline"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) } | |
| 36 | − | Knob { label: "rounding"; keywords: "corners radius rounded"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) } | |
| 37 | − | Switch { label: "resize on border"; hint: "drag window edges to resize"; checked: flick.v("general:resize_on_border", false); onToggled: on => HyprConf.set("general:resize_on_border", on) } | |
| 38 | − | ||
| 39 | − | SectionLabel { text: "opacity"; keywords: "transparency transparent alpha" } | |
| 40 | − | Knob { label: "focused"; keywords: "active window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:active_opacity", 1); onMoved: x => HyprConf.set("decoration:active_opacity", x) } | |
| 41 | − | Knob { label: "unfocused"; keywords: "inactive window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:inactive_opacity", 1); onMoved: x => HyprConf.set("decoration:inactive_opacity", x) } | |
| 42 | − | Switch { label: "dim unfocused"; keywords: "inactive windows darken"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) } | |
| 32 | + | Group { | |
| 33 | + | title: "gaps & borders" | |
| 34 | + | keywords: "spacing margins" | |
| 35 | + | Knob { label: "inner gaps"; keywords: "spacing padding gaps_in"; unit: "px"; from: 0; to: 30; value: flick.v("general:gaps_in", 0); onMoved: x => HyprConf.set("general:gaps_in", x) } | |
| 36 | + | Knob { label: "outer gaps"; keywords: "spacing margin gaps_out"; unit: "px"; from: 0; to: 40; value: flick.v("general:gaps_out", 0); onMoved: x => HyprConf.set("general:gaps_out", x) } | |
| 37 | + | Knob { label: "border"; keywords: "border size thickness outline"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) } | |
| 38 | + | Knob { label: "rounding"; keywords: "corners radius rounded"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) } | |
| 39 | + | Switch { label: "resize on border"; hint: "drag window edges to resize"; checked: flick.v("general:resize_on_border", false); onToggled: on => HyprConf.set("general:resize_on_border", on) } | |
| 40 | + | } | |
| 43 | 41 | ||
| 44 | − | SectionLabel { text: "effects" } | |
| 45 | − | Switch { label: "blur"; keywords: "frosted glass"; hint: "behind transparent windows and layers"; checked: flick.v("decoration:blur:enabled", false); onToggled: on => HyprConf.set("decoration:blur:enabled", on) } | |
| 46 | − | Knob { label: "blur size"; from: 1; to: 15; value: flick.v("decoration:blur:size", 1); onMoved: x => HyprConf.set("decoration:blur:size", x) } | |
| 47 | − | Knob { label: "blur passes"; from: 1; to: 6; value: flick.v("decoration:blur:passes", 1); onMoved: x => HyprConf.set("decoration:blur:passes", x) } | |
| 48 | − | Switch { label: "blur x-ray"; hint: "floating windows blur the wallpaper, not what's under them"; checked: flick.v("decoration:blur:xray", false); onToggled: on => HyprConf.set("decoration:blur:xray", on) } | |
| 49 | − | Switch { label: "shadows"; keywords: "drop shadow"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) } | |
| 50 | − | Switch { label: "animations"; keywords: "window motion transitions"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) } | |
| 42 | + | Group { | |
| 43 | + | title: "opacity" | |
| 44 | + | keywords: "transparency transparent alpha" | |
| 45 | + | Knob { label: "focused"; keywords: "active window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:active_opacity", 1); onMoved: x => HyprConf.set("decoration:active_opacity", x) } | |
| 46 | + | Knob { label: "unfocused"; keywords: "inactive window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:inactive_opacity", 1); onMoved: x => HyprConf.set("decoration:inactive_opacity", x) } | |
| 47 | + | Switch { label: "dim unfocused"; keywords: "inactive windows darken"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) } | |
| 48 | + | } | |
| 51 | 49 | ||
| 52 | − | SectionLabel { text: "layout" } | |
| 53 | − | Choice { | |
| 54 | − | label: "tiling layout" | |
| 55 | − | keywords: "tiling mode" | |
| 56 | − | options: [{ value: "master", label: "master" }, { value: "dwindle", label: "dwindle" }] | |
| 57 | − | value: flick.v("general:layout", "") | |
| 58 | − | onPicked: x => HyprConf.set("general:layout", x) | |
| 50 | + | Group { | |
| 51 | + | title: "effects" | |
| 52 | + | Switch { label: "blur"; keywords: "frosted glass"; hint: "behind transparent windows and layers"; checked: flick.v("decoration:blur:enabled", false); onToggled: on => HyprConf.set("decoration:blur:enabled", on) } | |
| 53 | + | Knob { label: "blur size"; from: 1; to: 15; value: flick.v("decoration:blur:size", 1); onMoved: x => HyprConf.set("decoration:blur:size", x) } | |
| 54 | + | Knob { label: "blur passes"; from: 1; to: 6; value: flick.v("decoration:blur:passes", 1); onMoved: x => HyprConf.set("decoration:blur:passes", x) } | |
| 55 | + | Switch { label: "blur x-ray"; hint: "floating windows blur the wallpaper, not what's under them"; checked: flick.v("decoration:blur:xray", false); onToggled: on => HyprConf.set("decoration:blur:xray", on) } | |
| 56 | + | Switch { label: "shadows"; keywords: "drop shadow"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) } | |
| 57 | + | Switch { label: "animations"; keywords: "window motion transitions"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) } | |
| 59 | 58 | } | |
| 60 | − | ColumnLayout { | |
| 61 | − | Layout.fillWidth: true | |
| 62 | − | visible: flick.v("general:layout", "") === "master" | |
| 63 | − | spacing: 8 | |
| 64 | − | Knob { label: "master width"; keywords: "mfact ratio split size"; from: 0.1; to: 0.9; decimals: 2; value: flick.v("master:mfact", 0.55); onMoved: x => HyprConf.set("master:mfact", x) } | |
| 59 | + | ||
| 60 | + | Group { | |
| 61 | + | title: "layout" | |
| 65 | 62 | Choice { | |
| 66 | − | label: "new window" | |
| 67 | − | keywords: "placement" | |
| 68 | − | options: [{ value: "master", label: "master" }, { value: "slave", label: "stack" }, { value: "inherit", label: "inherit" }] | |
| 69 | − | value: flick.v("master:new_status", "") | |
| 70 | − | onPicked: x => HyprConf.set("master:new_status", x) | |
| 63 | + | label: "tiling layout" | |
| 64 | + | keywords: "tiling mode" | |
| 65 | + | options: [{ value: "master", label: "master" }, { value: "dwindle", label: "dwindle" }] | |
| 66 | + | value: flick.v("general:layout", "") | |
| 67 | + | onPicked: x => HyprConf.set("general:layout", x) | |
| 71 | 68 | } | |
| 72 | − | Choice { | |
| 73 | − | label: "master side" | |
| 74 | − | keywords: "orientation" | |
| 75 | − | options: [{ value: "left", label: "left" }, { value: "right", label: "right" }, { value: "top", label: "top" }, | |
| 76 | − | { value: "bottom", label: "bottom" }, { value: "center", label: "center" }] | |
| 77 | − | value: flick.v("master:orientation", "") | |
| 78 | − | onPicked: x => HyprConf.set("master:orientation", x) | |
| 69 | + | ColumnLayout { | |
| 70 | + | Layout.fillWidth: true | |
| 71 | + | visible: flick.v("general:layout", "") === "master" | |
| 72 | + | spacing: 4 | |
| 73 | + | Knob { label: "master width"; keywords: "mfact ratio split size"; from: 0.1; to: 0.9; decimals: 2; value: flick.v("master:mfact", 0.55); onMoved: x => HyprConf.set("master:mfact", x) } | |
| 74 | + | Choice { | |
| 75 | + | label: "new window" | |
| 76 | + | keywords: "placement" | |
| 77 | + | options: [{ value: "master", label: "master" }, { value: "slave", label: "stack" }, { value: "inherit", label: "inherit" }] | |
| 78 | + | value: flick.v("master:new_status", "") | |
| 79 | + | onPicked: x => HyprConf.set("master:new_status", x) | |
| 80 | + | } | |
| 81 | + | Choice { | |
| 82 | + | label: "master side" | |
| 83 | + | keywords: "orientation" | |
| 84 | + | options: [{ value: "left", label: "left" }, { value: "right", label: "right" }, { value: "top", label: "top" }, | |
| 85 | + | { value: "bottom", label: "bottom" }, { value: "center", label: "center" }] | |
| 86 | + | value: flick.v("master:orientation", "") | |
| 87 | + | onPicked: x => HyprConf.set("master:orientation", x) | |
| 88 | + | } | |
| 89 | + | Switch { label: "new windows on top of the stack"; checked: flick.v("master:new_on_top", false); onToggled: on => HyprConf.set("master:new_on_top", on) } | |
| 90 | + | } | |
| 91 | + | ColumnLayout { | |
| 92 | + | Layout.fillWidth: true | |
| 93 | + | visible: flick.v("general:layout", "") === "dwindle" | |
| 94 | + | spacing: 4 | |
| 95 | + | Switch { label: "preserve split"; hint: "keep the split direction when windows move"; checked: flick.v("dwindle:preserve_split", false); onToggled: on => HyprConf.set("dwindle:preserve_split", on) } | |
| 96 | + | Switch { label: "smart split"; hint: "split by where the cursor is"; checked: flick.v("dwindle:smart_split", false); onToggled: on => HyprConf.set("dwindle:smart_split", on) } | |
| 79 | 97 | } | |
| 80 | − | Switch { label: "new windows on top of the stack"; checked: flick.v("master:new_on_top", false); onToggled: on => HyprConf.set("master:new_on_top", on) } | |
| 81 | − | } | |
| 82 | − | ColumnLayout { | |
| 83 | − | Layout.fillWidth: true | |
| 84 | − | visible: flick.v("general:layout", "") === "dwindle" | |
| 85 | − | spacing: 8 | |
| 86 | − | Switch { label: "preserve split"; hint: "keep the split direction when windows move"; checked: flick.v("dwindle:preserve_split", false); onToggled: on => HyprConf.set("dwindle:preserve_split", on) } | |
| 87 | − | Switch { label: "smart split"; hint: "split by where the cursor is"; checked: flick.v("dwindle:smart_split", false); onToggled: on => HyprConf.set("dwindle:smart_split", on) } | |
| 88 | 98 | } | |
| 89 | 99 | ||
| 90 | − | SectionLabel { text: "fullscreen & focus" } | |
| 91 | − | Choice { | |
| 92 | − | label: "focus under fullscreen" | |
| 93 | − | options: [{ value: 0, label: "ignore" }, { value: 1, label: "take over" }, { value: 2, label: "exit fullscreen" }] | |
| 94 | − | value: flick.v("misc:on_focus_under_fullscreen", -1) | |
| 95 | − | onPicked: x => HyprConf.set("misc:on_focus_under_fullscreen", x) | |
| 100 | + | Group { | |
| 101 | + | title: "fullscreen & focus" | |
| 102 | + | Choice { | |
| 103 | + | label: "focus under fullscreen" | |
| 104 | + | options: [{ value: 0, label: "ignore" }, { value: 1, label: "take over" }, { value: 2, label: "exit fullscreen" }] | |
| 105 | + | value: flick.v("misc:on_focus_under_fullscreen", -1) | |
| 106 | + | onPicked: x => HyprConf.set("misc:on_focus_under_fullscreen", x) | |
| 107 | + | } | |
| 108 | + | Switch { label: "closing keeps fullscreen"; hint: "the next window takes over when the fullscreen one closes (dwm monocle)"; checked: flick.v("misc:exit_window_retains_fullscreen", false); onToggled: on => HyprConf.set("misc:exit_window_retains_fullscreen", on) } | |
| 109 | + | Switch { label: "focus on activate"; hint: "apps asking for attention get focused"; checked: flick.v("misc:focus_on_activate", false); onToggled: on => HyprConf.set("misc:focus_on_activate", on) } | |
| 96 | 110 | } | |
| 97 | − | Switch { label: "closing keeps fullscreen"; hint: "the next window takes over when the fullscreen one closes (dwm monocle)"; checked: flick.v("misc:exit_window_retains_fullscreen", false); onToggled: on => HyprConf.set("misc:exit_window_retains_fullscreen", on) } | |
| 98 | − | Switch { label: "focus on activate"; hint: "apps asking for attention get focused"; checked: flick.v("misc:focus_on_activate", false); onToggled: on => HyprConf.set("misc:focus_on_activate", on) } | |
| 99 | 111 | ||
| 100 | − | Item { Layout.preferredHeight: 6 } | |
| 101 | 112 | RowLayout { | |
| 102 | 113 | Layout.fillWidth: true | |
| 103 | 114 | Text { | |
| @@ -116,5 +127,6 @@ Flickable { | |||
| 116 | 127 | onClicked: HyprConf.resetAll(["general:", "decoration:", "animations:", "master:", "dwindle:", "misc:"]) | |
| 117 | 128 | } | |
| 118 | 129 | } | |
| 130 | + | Item { Layout.preferredHeight: 12 } | |
| 119 | 131 | } | |
| 120 | 132 | } | |
| @@ -3,8 +3,9 @@ import QtQuick.Layouts | |||
| 3 | 3 | import Quickshell | |
| 4 | 4 | ||
| 5 | 5 | // The settings app: a normal Hyprland window (class org.quickshell, title "aurora settings"), opened maximized by a | |
| 6 | − | // window rule. The sections as a column of Doto words (the selected one large with the live dot), | |
| 7 | − | // the page on the right under a caption, key hints at the bottom. One Loader, so only the shown section runs. | |
| 6 | + | // window rule. Android-style: on the left the search pill, the sections (icon, name, caption; the shown one as a tonal | |
| 7 | + | // pill) and key hints; on the right the page under its Doto title, its settings grouped on cards (`Group`). | |
| 8 | + | // One Loader, so only the shown section runs. | |
| 8 | 9 | // Tab / Shift+Tab, Up/Down (outside text fields) or Ctrl+J/K = sections, Esc closes. | |
| 9 | 10 | // Ctrl+F (or the search button) searches every setting (SetIndex): the results replace the page, Enter / click opens | |
| 10 | 11 | // the section, scrolls to the row and flashes it. | |
| @@ -32,6 +33,9 @@ FloatingWindow { | |||
| 32 | 33 | for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i | |
| 33 | 34 | return 0 | |
| 34 | 35 | } | |
| 36 | + | onIndexChanged: navList.positionViewAtIndex(index, ListView.Contain) | |
| 37 | + | function secName(s) { return s.word.charAt(0).toUpperCase() + s.word.slice(1) } | |
| 38 | + | function secSub(s) { return s.caption.replace(/^[^·]*·\s*/, "") } | |
| 35 | 39 | function cycle(d) { sectionRequested(sections[(index + d + sections.length) % sections.length].id) } | |
| 36 | 40 | // typing in a field: arrows belong to it | |
| 37 | 41 | function inText() { var a = win.activeFocusItem; return a !== null && a.cursorPosition !== undefined } | |
| @@ -83,14 +87,18 @@ FloatingWindow { | |||
| 83 | 87 | if (e.section !== section) sectionRequested(e.section) | |
| 84 | 88 | landTimer.restart() | |
| 85 | 89 | } | |
| 86 | − | // the page's items in document order: past the group's SectionLabel, the first one with this label | |
| 90 | + | // the page's items in document order: past the group's Group card (or SectionLabel), the first one with this label | |
| 87 | 91 | function findRow(e) { | |
| 88 | 92 | var hit = null, header = null, past = e.group === "" | |
| 89 | 93 | function walk(it) { | |
| 90 | 94 | var kids = it.children | |
| 91 | 95 | for (var i = 0; i < kids.length && !hit; i++) { | |
| 92 | 96 | var c = kids[i] | |
| 93 | − | if (e.group !== "" && c.actionClicked !== undefined && c.text === e.group) { | |
| 97 | + | if (e.group !== "" && c.flat !== undefined && c.gap !== undefined && c.title === e.group) { | |
| 98 | + | past = true | |
| 99 | + | header = c | |
| 100 | + | if (e.kind === "group") { hit = c; return } | |
| 101 | + | } else if (e.group !== "" && c.actionClicked !== undefined && c.text === e.group && c.parent !== header) { | |
| 94 | 102 | past = true | |
| 95 | 103 | header = c | |
| 96 | 104 | if (e.kind === "group") { hit = c; return } | |
| @@ -169,167 +177,195 @@ FloatingWindow { | |||
| 169 | 177 | anchors.fill: parent | |
| 170 | 178 | Keys.forwardTo: [root] | |
| 171 | 179 | ||
| 172 | − | // ---- sections ---- | |
| 173 | − | Column { | |
| 174 | − | id: words | |
| 175 | − | x: Math.round(Math.min(150, win.width * 0.07)) | |
| 176 | − | anchors.verticalCenter: parent.verticalCenter | |
| 177 | − | spacing: 12 | |
| 178 | − | Repeater { | |
| 180 | + | // ---- the nav: search pill, sections, key hints ---- | |
| 181 | + | Item { | |
| 182 | + | id: nav | |
| 183 | + | x: 40 | |
| 184 | + | y: 40 | |
| 185 | + | width: 340 | |
| 186 | + | height: parent.height - 80 | |
| 187 | + | ||
| 188 | + | Rectangle { | |
| 189 | + | id: pill | |
| 190 | + | width: parent.width | |
| 191 | + | height: 52 | |
| 192 | + | radius: height / 2 | |
| 193 | + | color: Theme.s2 | |
| 194 | + | border.width: 1 | |
| 195 | + | border.color: searchInput.activeFocus ? Theme.fill : Theme.s2 | |
| 196 | + | MouseArea { | |
| 197 | + | anchors.fill: parent | |
| 198 | + | cursorShape: Qt.IBeamCursor | |
| 199 | + | onClicked: win.startSearch() | |
| 200 | + | } | |
| 201 | + | Icon { | |
| 202 | + | id: lens | |
| 203 | + | x: 20 | |
| 204 | + | anchors.verticalCenter: parent.verticalCenter | |
| 205 | + | text: Theme.iSearch | |
| 206 | + | color: Theme.grey | |
| 207 | + | font.pixelSize: 20 | |
| 208 | + | } | |
| 209 | + | TextInput { | |
| 210 | + | id: searchInput | |
| 211 | + | anchors.left: lens.right | |
| 212 | + | anchors.leftMargin: 14 | |
| 213 | + | anchors.right: parent.right | |
| 214 | + | anchors.rightMargin: 20 | |
| 215 | + | anchors.verticalCenter: parent.verticalCenter | |
| 216 | + | font.family: Theme.ui | |
| 217 | + | font.pixelSize: 16 | |
| 218 | + | color: Theme.fg | |
| 219 | + | selectionColor: Theme.fill | |
| 220 | + | selectedTextColor: Theme.fillFg | |
| 221 | + | clip: true | |
| 222 | + | cursorDelegate: Rectangle { | |
| 223 | + | width: 2 | |
| 224 | + | color: Theme.live | |
| 225 | + | visible: searchInput.activeFocus | |
| 226 | + | Timer { running: searchInput.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 227 | + | } | |
| 228 | + | Text { | |
| 229 | + | visible: !searchInput.text | |
| 230 | + | text: "search settings" | |
| 231 | + | font: searchInput.font | |
| 232 | + | color: Theme.mute | |
| 233 | + | } | |
| 234 | + | onTextChanged: { win.query = text; win.pick = 0 } | |
| 235 | + | // a click in the pill searches; leaving it empty ends the search | |
| 236 | + | onActiveFocusChanged: { | |
| 237 | + | if (activeFocus) win.searching = true | |
| 238 | + | else if (win.searching && text.trim() === "") win.stopSearch() | |
| 239 | + | } | |
| 240 | + | // handled here, so the window's Tab / Ctrl+J/K / Esc don't fire | |
| 241 | + | Keys.onPressed: e => { | |
| 242 | + | var ctrl = e.modifiers & Qt.ControlModifier, n = win.results.length | |
| 243 | + | if (e.key === Qt.Key_Escape) win.stopSearch() | |
| 244 | + | else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) { if (n) win.go(win.results[win.pick]) } | |
| 245 | + | else if (e.key === Qt.Key_Down || e.key === Qt.Key_Tab || (ctrl && e.key === Qt.Key_J)) { if (n) win.pick = (win.pick + 1) % n } | |
| 246 | + | else if (e.key === Qt.Key_Up || e.key === Qt.Key_Backtab || (ctrl && e.key === Qt.Key_K)) { if (n) win.pick = (win.pick + n - 1) % n } | |
| 247 | + | else if (ctrl && e.key === Qt.Key_F) selectAll() | |
| 248 | + | else return | |
| 249 | + | e.accepted = true | |
| 250 | + | } | |
| 251 | + | } | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | ListView { | |
| 255 | + | id: navList | |
| 256 | + | anchors.top: pill.bottom | |
| 257 | + | anchors.topMargin: 20 | |
| 258 | + | anchors.bottom: hints.top | |
| 259 | + | anchors.bottomMargin: 20 | |
| 260 | + | width: parent.width | |
| 261 | + | clip: true | |
| 262 | + | spacing: 4 | |
| 263 | + | boundsBehavior: Flickable.StopAtBounds | |
| 179 | 264 | model: win.sections | |
| 180 | − | Row { | |
| 265 | + | delegate: Rectangle { | |
| 181 | 266 | id: w | |
| 182 | 267 | required property var modelData | |
| 183 | 268 | required property int index | |
| 184 | 269 | readonly property bool on: win.index === index | |
| 185 | − | spacing: 20 | |
| 270 | + | width: ListView.view.width | |
| 271 | + | height: 64 | |
| 272 | + | radius: height / 2 | |
| 273 | + | color: on ? Theme.container : wm.hovered ? Theme.s1 : "transparent" | |
| 274 | + | Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } } | |
| 186 | 275 | // while searching, sections without a match fade | |
| 187 | 276 | opacity: !win.searching || win.query.trim() === "" || win.hitSections[modelData.id] ? 1 : 0.3 | |
| 188 | 277 | Behavior on opacity { enabled: Theme.motion; NumberAnimation { duration: Theme.t } } | |
| 189 | 278 | Rectangle { | |
| 279 | + | id: ic | |
| 280 | + | x: 10 | |
| 190 | 281 | anchors.verticalCenter: parent.verticalCenter | |
| 191 | − | visible: w.on | |
| 192 | − | width: 20 | |
| 193 | − | height: 20 | |
| 194 | − | radius: 10 | |
| 195 | − | color: Theme.live | |
| 282 | + | width: 44 | |
| 283 | + | height: 44 | |
| 284 | + | radius: 22 | |
| 285 | + | color: w.on ? Theme.fill : Theme.s2 | |
| 286 | + | Icon { | |
| 287 | + | anchors.centerIn: parent | |
| 288 | + | text: w.modelData.icon | |
| 289 | + | font.pixelSize: 20 | |
| 290 | + | color: w.on ? Theme.fillFg : Theme.fg | |
| 291 | + | } | |
| 196 | 292 | } | |
| 197 | − | Num { | |
| 198 | − | text: w.modelData.word.toUpperCase() | |
| 199 | − | font.pixelSize: w.on ? 76 : 32 | |
| 200 | − | color: w.on ? Theme.fg : wm.hovered ? Theme.grey : Theme.mute | |
| 201 | − | Behavior on font.pixelSize { | |
| 202 | − | enabled: Theme.motion | |
| 203 | − | NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } | |
| 293 | + | Column { | |
| 294 | + | anchors.left: ic.right | |
| 295 | + | anchors.leftMargin: 14 | |
| 296 | + | anchors.right: parent.right | |
| 297 | + | anchors.rightMargin: 16 | |
| 298 | + | anchors.verticalCenter: parent.verticalCenter | |
| 299 | + | spacing: 3 | |
| 300 | + | Text { | |
| 301 | + | width: parent.width | |
| 302 | + | elide: Text.ElideRight | |
| 303 | + | text: win.secName(w.modelData) | |
| 304 | + | color: w.on ? Theme.containerFg : Theme.fg | |
| 305 | + | font.family: Theme.ui | |
| 306 | + | font.pixelSize: 16 | |
| 307 | + | font.weight: w.on ? Font.DemiBold : Font.Normal | |
| 308 | + | } | |
| 309 | + | Lb { | |
| 310 | + | width: parent.width | |
| 311 | + | elide: Text.ElideRight | |
| 312 | + | text: win.secSub(w.modelData) | |
| 313 | + | px: 10 | |
| 314 | + | color: w.on ? Theme.fade(Theme.containerFg, 0.7) : Theme.grey | |
| 204 | 315 | } | |
| 205 | − | Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } } | |
| 206 | 316 | } | |
| 207 | 317 | HoverHandler { id: wm; cursorShape: Qt.PointingHandCursor } | |
| 208 | 318 | TapHandler { onTapped: { win.stopSearch(); win.sectionRequested(w.modelData.id); root.forceActiveFocus() } } | |
| 209 | 319 | } | |
| 210 | 320 | } | |
| 321 | + | ||
| 322 | + | Flow { | |
| 323 | + | id: hints | |
| 324 | + | anchors.bottom: parent.bottom | |
| 325 | + | x: 16 | |
| 326 | + | width: parent.width - 16 | |
| 327 | + | spacing: 22 | |
| 328 | + | Repeater { | |
| 329 | + | model: win.searching ? ["↑↓ result", "↵ open", "esc back"] : ["ctrl+f search", "tab · ↑↓ section", "esc close"] | |
| 330 | + | Lb { required property string modelData; text: modelData; px: 10 } | |
| 331 | + | } | |
| 332 | + | } | |
| 211 | 333 | } | |
| 212 | 334 | ||
| 213 | − | // ---- the page ---- | |
| 335 | + | // ---- the page: Doto title + caption, then the section's cards (or the search results) ---- | |
| 214 | 336 | Item { | |
| 215 | 337 | id: pageBox | |
| 216 | − | x: Math.max(words.x + 560, Math.round(win.width * 0.42)) | |
| 217 | − | y: 80 | |
| 218 | − | width: Math.min(900, win.width - x - 80) | |
| 219 | − | height: parent.height - y - 110 | |
| 338 | + | readonly property real room: parent.width - nav.x - nav.width - 112 // right of the nav | |
| 339 | + | width: Math.min(980, room) | |
| 340 | + | x: nav.x + nav.width + 64 + Math.max(0, Math.round((room - width) / 2)) | |
| 341 | + | y: 40 | |
| 342 | + | height: parent.height - 80 | |
| 220 | 343 | ||
| 221 | − | // the caption + search button, or the search pill | |
| 222 | − | Item { | |
| 344 | + | ColumnLayout { | |
| 223 | 345 | id: head | |
| 224 | 346 | width: parent.width | |
| 225 | − | height: 52 | |
| 226 | − | Lb { | |
| 227 | − | x: 18 | |
| 228 | − | anchors.verticalCenter: parent.verticalCenter | |
| 229 | − | visible: !win.searching | |
| 230 | − | text: win.sections[win.index].caption | |
| 231 | − | } | |
| 232 | − | IconBtn { | |
| 233 | − | anchors.right: parent.right | |
| 234 | − | anchors.verticalCenter: parent.verticalCenter | |
| 235 | − | visible: !win.searching | |
| 236 | − | icon: Theme.iSearch | |
| 237 | − | label: "search" | |
| 238 | − | onClicked: win.startSearch() | |
| 347 | + | spacing: 10 | |
| 348 | + | Num { | |
| 349 | + | Layout.leftMargin: 14 | |
| 350 | + | text: (win.searching ? "search" : win.sections[win.index].word).toUpperCase() | |
| 351 | + | font.pixelSize: 56 | |
| 239 | 352 | } | |
| 240 | − | Rectangle { | |
| 241 | − | anchors.fill: parent | |
| 242 | − | visible: win.searching | |
| 243 | − | radius: height / 2 | |
| 244 | − | color: Theme.s2 | |
| 245 | − | Icon { | |
| 246 | − | id: lens | |
| 247 | − | x: 20 | |
| 248 | − | anchors.verticalCenter: parent.verticalCenter | |
| 249 | − | text: Theme.iSearch | |
| 250 | − | color: Theme.grey | |
| 251 | − | font.pixelSize: 20 | |
| 252 | − | } | |
| 253 | − | TextInput { | |
| 254 | − | id: searchInput | |
| 255 | − | anchors.left: lens.right | |
| 256 | − | anchors.leftMargin: 14 | |
| 257 | − | anchors.right: found.left | |
| 258 | − | anchors.rightMargin: 12 | |
| 259 | − | anchors.verticalCenter: parent.verticalCenter | |
| 260 | − | font.family: Theme.ui | |
| 261 | − | font.pixelSize: 18 | |
| 262 | − | color: Theme.fg | |
| 263 | − | selectionColor: Theme.fill | |
| 264 | − | selectedTextColor: Theme.fillFg | |
| 265 | − | clip: true | |
| 266 | − | cursorDelegate: Rectangle { | |
| 267 | − | width: 2 | |
| 268 | − | color: Theme.live | |
| 269 | − | visible: searchInput.activeFocus | |
| 270 | − | Timer { running: searchInput.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 271 | − | } | |
| 272 | − | Text { | |
| 273 | − | visible: !searchInput.text | |
| 274 | − | text: "search settings" | |
| 275 | − | font: searchInput.font | |
| 276 | − | color: Theme.mute | |
| 277 | − | } | |
| 278 | − | onTextChanged: { win.query = text; win.pick = 0 } | |
| 279 | − | // handled here, so the window's Tab / Ctrl+J/K / Esc don't fire | |
| 280 | − | Keys.onPressed: e => { | |
| 281 | − | var ctrl = e.modifiers & Qt.ControlModifier, n = win.results.length | |
| 282 | − | if (e.key === Qt.Key_Escape) win.stopSearch() | |
| 283 | − | else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) { if (n) win.go(win.results[win.pick]) } | |
| 284 | − | else if (e.key === Qt.Key_Down || e.key === Qt.Key_Tab || (ctrl && e.key === Qt.Key_J)) { if (n) win.pick = (win.pick + 1) % n } | |
| 285 | − | else if (e.key === Qt.Key_Up || e.key === Qt.Key_Backtab || (ctrl && e.key === Qt.Key_K)) { if (n) win.pick = (win.pick + n - 1) % n } | |
| 286 | − | else if (ctrl && e.key === Qt.Key_F) selectAll() | |
| 287 | − | else return | |
| 288 | − | e.accepted = true | |
| 289 | − | } | |
| 290 | − | } | |
| 291 | − | Lb { | |
| 292 | − | id: found | |
| 293 | − | anchors.right: parent.right | |
| 294 | − | anchors.rightMargin: 22 | |
| 295 | − | anchors.verticalCenter: parent.verticalCenter | |
| 296 | − | text: win.results.length ? (win.results.length - 1) + " found" : "" | |
| 297 | − | } | |
| 353 | + | Lb { | |
| 354 | + | Layout.leftMargin: 16 | |
| 355 | + | text: !win.searching ? win.secSub(win.sections[win.index]) | |
| 356 | + | : win.results.length ? (win.results.length - 1) + " found" | |
| 357 | + | : "type a setting, a word or a section" | |
| 298 | 358 | } | |
| 299 | 359 | } | |
| 300 | 360 | ||
| 301 | 361 | Item { | |
| 362 | + | id: body | |
| 302 | 363 | anchors.top: head.bottom | |
| 303 | − | anchors.topMargin: 16 | |
| 364 | + | anchors.topMargin: 28 | |
| 304 | 365 | anchors.left: parent.left | |
| 305 | 366 | anchors.right: parent.right | |
| 306 | 367 | anchors.bottom: parent.bottom | |
| 307 | 368 | ||
| 308 | − | // the found row's flash, behind the page (rows have no background of their own) | |
| 309 | − | Item { | |
| 310 | − | id: flashBox | |
| 311 | − | anchors.fill: parent | |
| 312 | − | clip: true | |
| 313 | − | Rectangle { | |
| 314 | − | id: flash | |
| 315 | − | // re-mapped while the page scrolls or relayouts | |
| 316 | − | readonly property rect r: { | |
| 317 | − | var t = win.hlTarget | |
| 318 | − | if (!t) return Qt.rect(0, 0, 0, 0) | |
| 319 | − | void ((win.hlFlick ? win.hlFlick.contentY : 0) + t.y + t.width + t.height + flashBox.width) | |
| 320 | − | return t.mapToItem(flashBox, 0, 0, t.width, t.height) | |
| 321 | − | } | |
| 322 | − | readonly property real pad: 4 | |
| 323 | − | visible: win.hlTarget !== null && !win.searching | |
| 324 | − | opacity: 0 | |
| 325 | − | width: parent.width | |
| 326 | − | y: r.y - pad | |
| 327 | − | height: r.height + 2 * pad | |
| 328 | − | radius: height / 2 | |
| 329 | − | color: Theme.fade(Theme.fill, 0.2) | |
| 330 | − | } | |
| 331 | − | } | |
| 332 | − | ||
| 333 | 369 | Loader { | |
| 334 | 370 | id: loader | |
| 335 | 371 | anchors.fill: parent | |
| @@ -352,99 +388,117 @@ FloatingWindow { | |||
| 352 | 388 | } | |
| 353 | 389 | } | |
| 354 | 390 | ||
| 355 | − | // ---- search results ---- | |
| 356 | − | Lb { | |
| 357 | − | x: 18 | |
| 358 | − | visible: win.searching && win.query.trim() === "" | |
| 359 | − | text: "type a setting, a word or a section" | |
| 360 | − | } | |
| 361 | − | ListView { | |
| 362 | − | id: list | |
| 363 | − | anchors.fill: parent | |
| 364 | − | visible: win.searching | |
| 365 | − | clip: true | |
| 366 | − | spacing: 2 | |
| 367 | − | boundsBehavior: Flickable.StopAtBounds | |
| 368 | − | model: win.results | |
| 369 | − | header: Item { | |
| 370 | − | width: list.width | |
| 371 | − | height: win.results.length === 1 ? 44 : 0 | |
| 372 | − | Lb { | |
| 373 | − | x: 18 | |
| 374 | − | anchors.verticalCenter: parent.verticalCenter | |
| 375 | − | visible: parent.height > 0 | |
| 376 | − | text: "no setting matches" | |
| 377 | − | } | |
| 378 | − | } | |
| 379 | − | delegate: Rectangle { | |
| 380 | − | id: r | |
| 381 | − | required property var modelData | |
| 382 | − | required property int index | |
| 383 | − | readonly property bool on: win.pick === index | |
| 384 | − | width: ListView.view.width | |
| 385 | − | height: Math.max(56, txt.implicitHeight + 20) | |
| 386 | − | radius: 28 | |
| 387 | − | color: on || rm.containsMouse ? Theme.s2 : "transparent" | |
| 388 | − | Rectangle { | |
| 389 | − | x: 20 | |
| 390 | − | anchors.verticalCenter: parent.verticalCenter | |
| 391 | − | visible: r.on | |
| 392 | − | width: 10 | |
| 393 | − | height: 10 | |
| 394 | − | radius: 5 | |
| 395 | − | color: Theme.live | |
| 391 | + | // ---- search results, on one card ---- | |
| 392 | + | Rectangle { | |
| 393 | + | width: parent.width | |
| 394 | + | height: Math.min(parent.height, list.contentHeight + 16) | |
| 395 | + | visible: win.searching && win.results.length > 0 | |
| 396 | + | radius: 36 | |
| 397 | + | color: Theme.s1 | |
| 398 | + | ListView { | |
| 399 | + | id: list | |
| 400 | + | anchors.fill: parent | |
| 401 | + | anchors.margins: 8 | |
| 402 | + | clip: true | |
| 403 | + | spacing: 2 | |
| 404 | + | boundsBehavior: Flickable.StopAtBounds | |
| 405 | + | model: win.results | |
| 406 | + | header: Item { | |
| 407 | + | width: list.width | |
| 408 | + | height: win.results.length === 1 ? 44 : 0 | |
| 409 | + | Lb { | |
| 410 | + | x: 18 | |
| 411 | + | anchors.verticalCenter: parent.verticalCenter | |
| 412 | + | visible: parent.height > 0 | |
| 413 | + | text: "no setting matches" | |
| 414 | + | } | |
| 396 | 415 | } | |
| 397 | − | ColumnLayout { | |
| 398 | − | id: txt | |
| 399 | − | anchors.left: parent.left | |
| 400 | − | anchors.leftMargin: 44 | |
| 401 | − | anchors.right: parent.right | |
| 402 | − | anchors.rightMargin: 18 | |
| 403 | − | anchors.verticalCenter: parent.verticalCenter | |
| 404 | − | spacing: 3 | |
| 405 | − | Text { | |
| 406 | − | Layout.fillWidth: true | |
| 407 | − | text: r.modelData.label | |
| 408 | − | color: Theme.fg | |
| 409 | − | elide: Text.ElideRight | |
| 410 | − | font.family: Theme.ui | |
| 411 | − | font.pixelSize: 15 | |
| 416 | + | delegate: Rectangle { | |
| 417 | + | id: r | |
| 418 | + | required property var modelData | |
| 419 | + | required property int index | |
| 420 | + | readonly property bool on: win.pick === index | |
| 421 | + | width: ListView.view.width | |
| 422 | + | height: Math.max(56, txt.implicitHeight + 20) | |
| 423 | + | radius: 28 | |
| 424 | + | color: on || rm.containsMouse ? Theme.s2 : "transparent" | |
| 425 | + | Rectangle { | |
| 426 | + | x: 20 | |
| 427 | + | anchors.verticalCenter: parent.verticalCenter | |
| 428 | + | visible: r.on | |
| 429 | + | width: 10 | |
| 430 | + | height: 10 | |
| 431 | + | radius: 5 | |
| 432 | + | color: Theme.live | |
| 412 | 433 | } | |
| 413 | − | RowLayout { | |
| 414 | − | Layout.fillWidth: true | |
| 415 | − | spacing: 12 | |
| 416 | − | Lb { text: win.crumb(r.modelData); px: 10 } | |
| 434 | + | ColumnLayout { | |
| 435 | + | id: txt | |
| 436 | + | anchors.left: parent.left | |
| 437 | + | anchors.leftMargin: 44 | |
| 438 | + | anchors.right: parent.right | |
| 439 | + | anchors.rightMargin: 18 | |
| 440 | + | anchors.verticalCenter: parent.verticalCenter | |
| 441 | + | spacing: 3 | |
| 417 | 442 | Text { | |
| 418 | 443 | Layout.fillWidth: true | |
| 419 | − | visible: r.modelData.hint !== "" | |
| 420 | − | text: r.modelData.hint | |
| 421 | − | color: Theme.fgDim | |
| 444 | + | text: r.modelData.label | |
| 445 | + | color: Theme.fg | |
| 422 | 446 | elide: Text.ElideRight | |
| 423 | 447 | font.family: Theme.ui | |
| 424 | − | font.pixelSize: 13 | |
| 448 | + | font.pixelSize: 15 | |
| 449 | + | } | |
| 450 | + | RowLayout { | |
| 451 | + | Layout.fillWidth: true | |
| 452 | + | spacing: 12 | |
| 453 | + | Lb { text: win.crumb(r.modelData); px: 10 } | |
| 454 | + | Text { | |
| 455 | + | Layout.fillWidth: true | |
| 456 | + | visible: r.modelData.hint !== "" | |
| 457 | + | text: r.modelData.hint | |
| 458 | + | color: Theme.fgDim | |
| 459 | + | elide: Text.ElideRight | |
| 460 | + | font.family: Theme.ui | |
| 461 | + | font.pixelSize: 13 | |
| 462 | + | } | |
| 425 | 463 | } | |
| 426 | 464 | } | |
| 427 | − | } | |
| 428 | − | MouseArea { | |
| 429 | − | id: rm | |
| 430 | − | anchors.fill: parent | |
| 431 | − | hoverEnabled: true | |
| 432 | − | cursorShape: Qt.PointingHandCursor | |
| 433 | − | onClicked: win.go(r.modelData) | |
| 465 | + | MouseArea { | |
| 466 | + | id: rm | |
| 467 | + | anchors.fill: parent | |
| 468 | + | hoverEnabled: true | |
| 469 | + | cursorShape: Qt.PointingHandCursor | |
| 470 | + | onClicked: win.go(r.modelData) | |
| 471 | + | } | |
| 434 | 472 | } | |
| 435 | 473 | } | |
| 436 | 474 | } | |
| 437 | − | } | |
| 438 | − | } | |
| 439 | 475 | ||
| 440 | − | Row { | |
| 441 | − | x: words.x | |
| 442 | − | anchors.bottom: parent.bottom | |
| 443 | − | anchors.bottomMargin: 50 | |
| 444 | − | spacing: 34 | |
| 445 | − | Repeater { | |
| 446 | − | model: win.searching ? ["↑↓ result", "↵ open", "esc back"] : ["ctrl+f search", "tab · ↑↓ section", "esc close"] | |
| 447 | − | Lb { required property string modelData; text: modelData } | |
| 476 | + | // the found row's flash, over the page (the cards are opaque); as wide as the row's column | |
| 477 | + | Item { | |
| 478 | + | id: flashBox | |
| 479 | + | anchors.fill: parent | |
| 480 | + | clip: true | |
| 481 | + | Rectangle { | |
| 482 | + | id: flash | |
| 483 | + | // re-mapped while the page scrolls or relayouts | |
| 484 | + | readonly property rect r: { | |
| 485 | + | var t = win.hlTarget | |
| 486 | + | if (!t || !t.parent) return Qt.rect(0, 0, 0, 0) | |
| 487 | + | void ((win.hlFlick ? win.hlFlick.contentY : 0) + t.y + t.width + t.height + flashBox.width) | |
| 488 | + | var a = t.mapToItem(flashBox, 0, 0, t.width, t.height) | |
| 489 | + | var p = t.parent.mapToItem(flashBox, 0, 0, t.parent.width, t.parent.height) | |
| 490 | + | return Qt.rect(p.x, a.y, p.width, a.height) | |
| 491 | + | } | |
| 492 | + | visible: win.hlTarget !== null && !win.searching | |
| 493 | + | opacity: 0 | |
| 494 | + | x: r.x | |
| 495 | + | y: r.y | |
| 496 | + | width: r.width | |
| 497 | + | height: r.height | |
| 498 | + | radius: Math.min(height / 2, 36) | |
| 499 | + | color: Theme.fade(Theme.fill, 0.2) | |
| 500 | + | } | |
| 501 | + | } | |
| 448 | 502 | } | |
| 449 | 503 | } | |
| 450 | 504 | } | |
| @@ -453,9 +507,9 @@ FloatingWindow { | |||
| 453 | 507 | Keys.onDownPressed: e => { if (win.inText()) { e.accepted = false; return } win.cycle(1) } | |
| 454 | 508 | } | |
| 455 | 509 | ||
| 456 | − | Component { id: netC; NetPage { active: true } } | |
| 457 | − | Component { id: btC; BtPage { active: true } } | |
| 458 | − | Component { id: soundC; AudioPage {} } | |
| 510 | + | Component { id: netC; NetPage { active: true; cards: true } } | |
| 511 | + | Component { id: btC; BtPage { active: true; cards: true } } | |
| 512 | + | Component { id: soundC; AudioPage { cards: true } } | |
| 459 | 513 | Component { id: appearanceC; SetAppearance { onOverlayRequested: name => win.overlayRequested(name) } } | |
| 460 | 514 | Component { id: shellC; SetShell {} } | |
| 461 | 515 | Component { id: windowsC; SetWindows {} } | |
| @@ -184,6 +184,7 @@ Singleton { | |||
| 184 | 184 | readonly property string iCast: cp(0xF0118) | |
| 185 | 185 | readonly property string iCrop: cp(0xF019E) | |
| 186 | 186 | readonly property string iWindow: cp(0xF05B0) | |
| 187 | + | readonly property string iWindows: cp(0xF05B2) // window-restore (settings > windows) | |
| 187 | 188 | readonly property string iMonitor: cp(0xF0379) | |
| 188 | 189 | readonly property string iSave: cp(0xF0193) | |
| 189 | 190 | readonly property string iClipboard: cp(0xF0147) | |