12 Commits
Author SHA1 Message Date
zach 656bd0a54d Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m37s
C++ / build (pull_request) Successful in 2m22s
2026-06-30 22:42:51 +02:00
zach 355106cb2b add radius anim to search results 2026-06-30 22:40:08 +02:00
zach 865b5bda53 chore: cleanup comments 2026-06-30 22:27:37 +02:00
zach 2b89c8e4a1 implement search bar functionality in settings 2026-06-30 22:20:48 +02:00
zach f01001dcfa merge main into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m42s
C++ / build (pull_request) Successful in 2m28s
2026-06-30 18:15:43 +02:00
zach 61019204d8 formatter 2026-06-30 15:35:25 +02:00
zach bc6b0d50ab Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 34s
Python / lint-format (pull_request) Successful in 35s
Python / test (pull_request) Successful in 1m11s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m58s
C++ / build (pull_request) Successful in 4m31s
2026-06-29 22:10:45 +02:00
zach f27c9afc25 more methods for wifi and scanning + layout
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-26 15:15:03 +02:00
Inorishio 5962fc8354 Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 17s
Python / lint-format (pull_request) Successful in 27s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-06-24 18:58:36 +02:00
Inorishio 1adcb9841c Added network properties, nicNames, netDevices, networkName < reminder I should use layouts.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m52s
2026-06-22 23:34:58 +02:00
Inorishio 0e0f8555ed fixing singleton in helpers for network + getting output from systray 2026-06-15 20:48:01 +02:00
Inorishio 1d63e67554 removed network module and moved it to tray, have a popout window without content! everything still in wi.i.p. 2026-06-13 18:22:03 +02:00
49 changed files with 1662 additions and 1610 deletions
+1
View File
@@ -15,3 +15,4 @@ dist/
**/target/ **/target/
**/test-plugins/ **/test-plugins/
**/Charts/ **/Charts/
**/network-dev/
+14
View File
@@ -51,6 +51,20 @@ add_compile_options(
-Wunreachable-code -Wunreachable-code
) )
if("shell" IN_LIST ENABLE_MODULES)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(SETTINGS_INDEX_JSON "${CMAKE_BINARY_DIR}/settings-index.json")
execute_process(
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/scripts/build-settings-index.py"
"${CMAKE_SOURCE_DIR}/Modules/Settings"
"${SETTINGS_INDEX_JSON}"
RESULT_VARIABLE SETTINGS_INDEX_RESULT
)
if(NOT SETTINGS_INDEX_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to build settings search index")
endif()
endif()
if("plugin" IN_LIST ENABLE_MODULES) if("plugin" IN_LIST ENABLE_MODULES)
add_subdirectory(Plugins) add_subdirectory(Plugins)
-4
View File
@@ -40,10 +40,6 @@ JsonObject {
id: "tray", id: "tray",
enabled: true enabled: true
}, },
{
id: "network",
enabled: false
},
{ {
id: "clock", id: "clock",
enabled: true enabled: true
+125
View File
@@ -0,0 +1,125 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
import QtQuick
Singleton {
id: root
readonly property list<NetworkDevice> devices: Networking.devices.values
readonly property list<Network> networks: initialBuildNetworks()
property bool scanning: false
readonly property list<WifiDevice> wifiDevices: devices.filter(d => wifiDevice(d))
readonly property bool wifiEnabled: Networking.wifiEnabled
// Original code
readonly property list<NetworkDevice> netDevice: Networking.devices.values
readonly property string networkName: getNetworkName()
readonly property list<string> nicNames: networkInterfaceCardNames()
// Useless will prob remove
function getConnectedDevices() {
let connectedDevices = [];
for (var i = 0; i < netDevice.length; i++) {
if (netDevice[i].connected === true) {
connectedDevices.push(netDevice[i].name);
}
}
return connectedDevices;
}
// SHOULD retrieve network names of connected devices
// Currently only gives connected nic name
function getNetworkName() {
const devices = netDevice.filter(device => device.connected === true);
for (var i = 0; i < devices.length; i++) {
return devices[i].name;
}
return "Failed network name";
}
// Searches wired/wireless devices and sets them in a list
function networkInterfaceCardNames() {
let nicList = [];
for (let i = 0; i < netDevice.length; ++i) {
nicList.push(netDevice[i].name);
}
return nicList;
}
//
function initialBuildNetworks(): void {
const init = [];
for (const d of wifiDevices) {
d.scannerEnabled = true;
init.push(...d.networks.values);
d.scannerEnabled = false;
}
networks = init;
}
function isSecure(security): bool {
return !(security === WifiSecurityType.Open);
}
function rebuildNetworks(): void {
if (!scanning)
return;
const next = [];
for (const d of wifiDevices) {
next.push(...d.networks.values);
}
networks = next;
}
function rescanWifi(): void {
scanning = true;
setScan(true);
scanTimer.restart();
}
function setScan(value: bool): void {
for (const d of wifiDevices) {
d.scannerEnabled = value;
}
}
function setWifi(value: bool): void {
Networking.wifiEnabled = value;
}
function wifiDevice(dev): bool {
return dev.type === DeviceType.Wifi;
}
Timer {
id: scanTimer
interval: 5000
repeat: false
onTriggered: {
root.rebuildNetworks();
root.setScan(false);
root.scanning = false;
}
}
Timer {
interval: 500
repeat: true
running: root.scanning
onTriggered: {
root.rebuildNetworks();
}
}
}
-10
View File
@@ -9,7 +9,6 @@ import qs.Config
import qs.Helpers import qs.Helpers
import qs.Modules.SysTray import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets import qs.Modules.SysTray.Widgets
import qs.Modules.Network
import qs.Modules.Updates import qs.Modules.Updates
RowLayout { RowLayout {
@@ -185,15 +184,6 @@ RowLayout {
} }
} }
DelegateChoice {
roleValue: "network"
delegate: WrappedLoader {
sourceComponent: NetworkWidget {
}
}
}
DelegateChoice { DelegateChoice {
roleValue: "media" roleValue: "media"
-1
View File
@@ -10,7 +10,6 @@ import qs.Config
import qs.Helpers import qs.Helpers
import qs.Modules.SysTray import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets import qs.Modules.SysTray.Widgets
import qs.Modules.Network
Item { Item {
id: root id: root
+4 -13
View File
@@ -34,21 +34,11 @@ Item {
implicitHeight: 50 implicitHeight: 50
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { SearchBar {
id: searchIcon
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
text: "search"
}
CustomTextField {
id: searchField id: searchField
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: searchIcon.right anchors.left: parent.left
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
@@ -170,7 +160,6 @@ Item {
id: lineText id: lineText
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2 height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
text: lineRow.modelData.text.trim() text: lineRow.modelData.text.trim()
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -336,6 +325,8 @@ Item {
} }
model: ScriptModel { model: ScriptModel {
values: ClipHistory.fuzzyQuery(searchField.text) values: ClipHistory.fuzzyQuery(searchField.text)
onValuesChanged: view.currentIndex = 0
} }
onCurrentItemChanged: { onCurrentItemChanged: {
-1
View File
@@ -6,7 +6,6 @@ import QtQuick
import qs.Config import qs.Config
import qs.Components import qs.Components
import qs.Modules.WSOverview import qs.Modules.WSOverview
import qs.Modules.Network
import qs.Modules.SysTray.Popouts import qs.Modules.SysTray.Popouts
import qs.Modules.Updates import qs.Modules.Updates
+1 -5
View File
@@ -52,11 +52,7 @@ Item {
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix) placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
topPadding: Appearance.padding.larger topPadding: Appearance.padding.larger
Component.onCompleted: { Component.onCompleted: forceActiveFocus()
console.log(search.color);
console.log(search.placeholderTextColor);
forceActiveFocus();
}
Keys.onDownPressed: list.currentList?.decrementCurrentIndex() Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => { Keys.onPressed: event => {
-36
View File
@@ -1,36 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Networking
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules
import qs.Helpers as Helpers
Item {
id: root
required property var wrapper
ColumnLayout {
id: layout
spacing: 8
Repeater {
model: Helpers.Network.devices
CustomRadioButton {
id: network
required property NetworkDevice modelData
checked: Helpers.Network.activeDevice?.name === modelData.name
text: modelData.description
visible: modelData.name !== "lo"
}
}
}
}
-25
View File
@@ -1,25 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
Item {
id: root
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: layout.implicitWidth
RowLayout {
id: layout
anchors.bottom: parent.bottom
anchors.top: parent.top
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
text: "android_wifi_4_bar"
}
}
}
+52
View File
@@ -3,12 +3,64 @@ import qs.Components
import qs.Config import qs.Config
CustomRect { CustomRect {
id: root
property bool first property bool first
property bool last property bool last
property string settingAnchor
function flashHighlight(): void {
flash.restart();
}
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
CustomRect {
id: highlight
anchors.fill: parent
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
color: DynamicColors.palette.m3primary
opacity: 0
radius: parent.radius
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
SequentialAnimation {
id: flash
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.normal
property: "opacity"
target: highlight
to: 0.08
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.extraLarge
property: "opacity"
target: highlight
to: 0
}
}
}
} }
+100 -2
View File
@@ -9,6 +9,7 @@ import qs.Config
ColumnLayout { ColumnLayout {
id: root id: root
property bool animateScroll: false
readonly property int cappedWidth: Math.min(800, width) readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild default property Item contentChild
readonly property alias flickable: flickable readonly property alias flickable: flickable
@@ -16,11 +17,100 @@ ColumnLayout {
required property SettingsState sState required property SettingsState sState
required property string title required property string title
function applySearchAnchor(): void {
if (!sState.searchAnchor)
return;
scrollRetry.tries = 0;
scrollRetry.lastHeight = -1;
scrollRetry.stableFrames = 0;
scrollRetry.restart();
}
function findAnchor(item: Item, anchor: string): Item {
if (!item)
return null;
if (item.settingAnchor !== undefined && item.settingAnchor === anchor)
return item;
const kids = item.children;
for (let i = 0; i < kids.length; i++) {
const found = findAnchor(kids[i], anchor);
if (found)
return found;
}
return null;
}
function highlightAnchor(anchor: string): void {
const row = findAnchor(contentChild, anchor);
if (row && row.flashHighlight !== undefined)
row.flashHighlight();
}
function scrollToAnchor(anchor: string): bool {
if (!anchor || !contentChild)
return false;
const row = findAnchor(contentChild, anchor);
if (!row)
return false;
const pos = row.mapToItem(flickable.contentItem, 0, 0);
const inset = flickable.height * flickable.fadeAmount + Appearance.padding.large;
const minY = -flickable.topMargin;
const maxY = Math.max(minY, flickable.contentHeight + flickable.bottomMargin - flickable.height);
const target = Math.max(minY, Math.min(pos.y - inset, maxY));
root.animateScroll = true;
flickable.contentY = target;
Qt.callLater(() => root.animateScroll = false);
if (row.flashHighlight !== undefined)
row.flashHighlight();
return true;
}
spacing: Appearance.spacing.large spacing: Appearance.spacing.large
MouseArea { // Prevent clicks from reaching flickable Component.onCompleted: applySearchAnchor()
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
Timer {
id: scrollRetry
property real lastHeight: -1
property int stableFrames: 0
property int tries: 0
interval: 16
repeat: true
onTriggered: {
const h = flickable.contentHeight;
if (h === lastHeight && h > flickable.height)
stableFrames++;
else
stableFrames = 0;
lastHeight = h;
const ready = stableFrames >= 3 || tries >= 30;
if (ready) {
if (root.scrollToAnchor(root.sState.searchAnchor))
root.sState.searchAnchor = "";
stop();
}
tries++;
}
}
Connections {
function onHighlightSetting(anchor: string): void {
root.highlightAnchor(anchor);
}
function onSearchAnchorChanged(): void {
root.applySearchAnchor();
}
target: root.sState
}
MouseArea {
Layout.bottomMargin: -flickable.topMargin
implicitHeight: header.implicitHeight - Layout.bottomMargin implicitHeight: header.implicitHeight - Layout.bottomMargin
implicitWidth: header.implicitWidth implicitWidth: header.implicitWidth
z: 1 z: 1
@@ -69,6 +159,14 @@ ColumnLayout {
fadeAmount: 0.1 fadeAmount: 0.1
topMargin: Appearance.padding.large topMargin: Appearance.padding.large
Behavior on contentY {
enabled: root.animateScroll
Anim {
type: Anim.DefaultSpatial
}
}
TapHandler { TapHandler {
onTapped: flickable.focus = true onTapped: flickable.focus = true
} }
+5
View File
@@ -9,8 +9,13 @@ CustomSwitch {
readonly property alias bg: bg readonly property alias bg: bg
property alias first: bg.first property alias first: bg.first
property alias last: bg.last property alias last: bg.last
property string settingAnchor
property string subtext property string subtext
function flashHighlight(): void {
bg.flashHighlight();
}
Layout.fillWidth: true Layout.fillWidth: true
cLayer: 2 cLayer: 2
horizontalPadding: Appearance.padding.largeIncreased horizontalPadding: Appearance.padding.largeIncreased
+6
View File
@@ -34,6 +34,12 @@ ColumnLayout {
target: root.sState target: root.sState
value: searchField.text.length > 0 value: searchField.text.length > 0
} }
Binding {
property: "searchText"
target: root.sState
value: searchField.text
}
} }
NavLocations { NavLocations {
+190 -3
View File
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules.Settings import qs.Modules.Settings
@@ -9,7 +10,33 @@ import qs.Modules.Settings
VerticalFadeFlickable { VerticalFadeFlickable {
id: root id: root
readonly property var groups: {
const out = [];
const byPage = ({});
for (const e of results) {
const key = e.pageIdx;
if (byPage[key] === undefined) {
byPage[key] = {
"pageIdx": e.pageIdx,
"page": e.crumbLabels[0],
"icon": e.crumbIcons[0],
"entries": []
};
out.push(byPage[key]);
}
byPage[key].entries.push(e);
}
return out;
}
readonly property var results: {
if (!searching)
return [];
const all = SettingsSearcher.query(search);
return all;
}
required property SettingsState sState required property SettingsState sState
readonly property string search: sState.searchText
readonly property bool searching: search.length > 0
bottomMargin: Appearance.padding.large bottomMargin: Appearance.padding.large
contentHeight: content.implicitHeight contentHeight: content.implicitHeight
@@ -30,14 +57,14 @@ VerticalFadeFlickable {
Repeater { Repeater {
id: list id: list
model: PageRegistry.pages model: root.searching ? [] : PageRegistry.pages
CustomRect { CustomRect {
id: item id: item
required property int index required property int index
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1]?.category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1]?.category !== modelData.category
readonly property bool isCurrentPage: index === root.sState.currentPageIdx readonly property bool isCurrentPage: index === root.sState.currentPageIdx
required property var modelData required property var modelData
@@ -120,6 +147,166 @@ VerticalFadeFlickable {
} }
} }
} }
ListView {
id: resultList
Layout.fillWidth: true
cacheBuffer: 10000
implicitHeight: contentHeight
interactive: false
spacing: Appearance.padding.large
delegate: ColumnLayout {
id: group
required property int index
required property var modelData
spacing: Appearance.spacing.small
width: resultList.width
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
text: group.modelData.icon
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3primary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: group.modelData.page
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
Repeater {
model: group.modelData.entries
CustomRect {
id: result
required property int index
readonly property bool isFirst: index === 0
readonly property bool isLast: index === group.modelData.entries.length - 1
required property var modelData
Layout.fillWidth: true
bottomLeftRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: {
const h = resultLayout.implicitHeight + resultLayout.anchors.margins * 2;
return h % 2 === 0 ? h : h + 1;
}
topLeftRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
ColumnLayout {
id: resultLayout
anchors.fill: parent
anchors.margins: Appearance.padding.large
anchors.rightMargin: result.modelData.togglePath ? toggle.width + Appearance.padding.large * 2 : Appearance.padding.large
spacing: Appearance.spacing.small / 2
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: {
const labels = result.modelData.crumbLabels.slice(1);
const section = result.modelData.section;
const parts = section && section !== labels[labels.length - 1] ? labels.concat(section) : labels;
return parts.join(" \u203a ");
}
visible: text.length > 0
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Appearance.font.size.medium
text: SettingsSearcher.highlight(result.modelData.title, root.search, DynamicColors.palette.m3primary)
textFormat: Text.StyledText
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: SettingsSearcher.highlight(result.modelData.subtext, root.search, DynamicColors.palette.m3primary)
textFormat: Text.StyledText
visible: result.modelData.subtext.length > 0
}
}
StateLayer {
id: layer
z: 1
onClicked: {
root.sState.jumpToSetting(result.modelData.pageIdx, result.modelData.subPath, result.modelData.anchor);
}
}
CustomSwitch {
id: toggle
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
cLayer: 3
checked: result.modelData.toggleValue
scale: 0.85
transformOrigin: Item.Right
visible: result.modelData.togglePath
z: 2
onToggled: result.modelData.setToggle(checked)
}
}
}
}
}
model: ScriptModel {
objectProp: "pageIdx"
values: root.groups
}
}
CustomText {
Layout.fillWidth: true
Layout.topMargin: Appearance.padding.large
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
horizontalAlignment: Text.AlignHCenter
text: qsTr("No matching settings")
visible: root.searching && root.results.length === 0
}
} }
component RadiusBehavior: Behavior { component RadiusBehavior: Behavior {
+5
View File
@@ -30,6 +30,7 @@ PageBase {
DefaultRow { DefaultRow {
first: true first: true
icon: "terminal" icon: "terminal"
settingAnchor: "apps-default-terminal"
status: Config.general.apps.terminal.join(" ") status: Config.general.apps.terminal.join(" ")
text: qsTr("Terminal") text: qsTr("Terminal")
@@ -38,6 +39,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "volume_up" icon: "volume_up"
settingAnchor: "apps-default-audio"
status: Config.general.apps.audio.join(" ") status: Config.general.apps.audio.join(" ")
text: qsTr("Audio") text: qsTr("Audio")
@@ -46,6 +48,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "play_circle" icon: "play_circle"
settingAnchor: "apps-default-playback"
status: Config.general.apps.playback.join(" ") status: Config.general.apps.playback.join(" ")
text: qsTr("Media playback") text: qsTr("Media playback")
@@ -55,6 +58,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "folder" icon: "folder"
last: true last: true
settingAnchor: "apps-default-file-manager"
status: Config.general.apps.explorer.join(" ") status: Config.general.apps.explorer.join(" ")
text: qsTr("File manager") text: qsTr("File manager")
@@ -70,6 +74,7 @@ PageBase {
first: true first: true
icon: "apps" icon: "apps"
last: true last: true
settingAnchor: "apps-all-apps"
status: qsTr("Browse installed apps, set favorites and hidden") status: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps") text: qsTr("All apps")
+2
View File
@@ -25,6 +25,7 @@ PageBase {
first: true first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted) icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output") label: qsTr("Output")
settingAnchor: "audio-output"
value: Audio.volume value: Audio.volume
valueLabel: Math.round(value * 100) + "%" valueLabel: Math.round(value * 100) + "%"
@@ -55,6 +56,7 @@ PageBase {
first: true first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted) icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input") label: qsTr("Input")
settingAnchor: "audio-input"
value: Audio.sourceVolume value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%" valueLabel: Math.round(value * 100) + "%"
@@ -50,6 +50,7 @@ PageBase {
first: true first: true
last: true last: true
menuItems: root.clockFormats menuItems: root.clockFormats
settingAnchor: "bar-clock-format"
subtext: qsTr("Change how time is displayed in the widget") subtext: qsTr("Change how time is displayed in the widget")
text: qsTr("Time format") text: qsTr("Time format")
@@ -25,6 +25,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showAudio checked: Config.bar.tray.showAudio
first: true first: true
settingAnchor: "bar-status-speakers"
text: qsTr("Speakers") text: qsTr("Speakers")
onToggled: Config.bar.tray.showAudio = checked onToggled: Config.bar.tray.showAudio = checked
@@ -32,6 +33,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showMicrophone checked: Config.bar.tray.showMicrophone
settingAnchor: "bar-status-mic"
text: qsTr("Microphone") text: qsTr("Microphone")
onToggled: Config.bar.tray.showMicrophone = checked onToggled: Config.bar.tray.showMicrophone = checked
@@ -62,6 +64,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showPower checked: Config.bar.tray.showPower
last: true last: true
settingAnchor: "bar-status-power"
text: qsTr("Battery") text: qsTr("Battery")
onToggled: Config.bar.tray.showPower = checked onToggled: Config.bar.tray.showPower = checked
@@ -75,6 +78,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.popouts.audio checked: Config.bar.popouts.audio
first: true first: true
settingAnchor: "bar-status-audio-popout"
subtext: qsTr("Show a details popout when hovering the audio icons") subtext: qsTr("Show a details popout when hovering the audio icons")
text: qsTr("Audio popout on hover") text: qsTr("Audio popout on hover")
@@ -84,6 +88,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.popouts.upower checked: Config.bar.popouts.upower
last: true last: true
settingAnchor: "bar-status-power-popout"
subtext: qsTr("Show a details popout when hovering the power icon") subtext: qsTr("Show a details popout when hovering the power icon")
text: qsTr("Power popout on hover") text: qsTr("Power popout on hover")
@@ -20,6 +20,7 @@ PageBase {
first: true first: true
from: 12 from: 12
last: true last: true
settingAnchor: "bar-tray-iconsize"
stepSize: 1 stepSize: 1
text: qsTr("Icon size") text: qsTr("Icon size")
to: 24 to: 24
@@ -26,6 +26,7 @@ PageBase {
checked: Config.bar.autoHide checked: Config.bar.autoHide
first: true first: true
last: true last: true
settingAnchor: "bar-autohide"
subtext: qsTr("Hide the bar, reveal on hover") subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide") text: qsTr("Auto hide")
@@ -40,6 +41,7 @@ PageBase {
NavRow { NavRow {
first: true first: true
icon: "widgets" icon: "widgets"
settingAnchor: "bar-tray"
status: qsTr("System tray icons") status: qsTr("System tray icons")
text: qsTr("Tray") text: qsTr("Tray")
@@ -48,6 +50,7 @@ PageBase {
NavRow { NavRow {
icon: "signal_cellular_alt" icon: "signal_cellular_alt"
settingAnchor: "bar-statusicons"
status: qsTr("Visible indicators") status: qsTr("Visible indicators")
text: qsTr("Status icons") text: qsTr("Status icons")
@@ -57,6 +60,7 @@ PageBase {
NavRow { NavRow {
icon: "schedule" icon: "schedule"
last: true last: true
settingAnchor: "bar-clock"
status: qsTr("Date, icon, background") status: qsTr("Date, icon, background")
text: qsTr("Clock") text: qsTr("Clock")
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.enabled checked: Config.dashboard.enabled
first: true first: true
last: true last: true
settingAnchor: "dashboard-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.dashboard.enabled = checked onToggled: Config.dashboard.enabled = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.launcher.enabled checked: Config.launcher.enabled
first: true first: true
last: true last: true
settingAnchor: "launcher-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.launcher.enabled = checked onToggled: Config.launcher.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "launcher-max-items-shown"
stepSize: 1 stepSize: 1
text: qsTr("Max items shown") text: qsTr("Max items shown")
to: 20 to: 20
@@ -51,6 +53,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "launcher-max-wallpapers"
stepSize: 1 stepSize: 1
text: qsTr("Max wallpapers") text: qsTr("Max wallpapers")
to: 30 to: 30
@@ -62,6 +65,7 @@ PageBase {
SpinRow { SpinRow {
from: 0 from: 0
last: true last: true
settingAnchor: "launcher-drag-threshold"
stepSize: 5 stepSize: 5
subtext: qsTr("Pixels dragged before the launcher opens") subtext: qsTr("Pixels dragged before the launcher opens")
text: qsTr("Drag threshold") text: qsTr("Drag threshold")
@@ -80,6 +84,7 @@ PageBase {
checked: Config.launcher.enableDangerousActions checked: Config.launcher.enableDangerousActions
first: true first: true
last: true last: true
settingAnchor: "launcher-enable-dangerous-actions"
subtext: qsTr("Allow actions that shut down or log out") subtext: qsTr("Allow actions that shut down or log out")
text: qsTr("Enable dangerous actions") text: qsTr("Enable dangerous actions")
@@ -94,6 +99,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.apps checked: Config.launcher.useFuzzy.apps
first: true first: true
settingAnchor: "launcher-apps"
text: qsTr("Apps") text: qsTr("Apps")
onToggled: Config.launcher.useFuzzy.apps = checked onToggled: Config.launcher.useFuzzy.apps = checked
@@ -101,20 +107,15 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.actions checked: Config.launcher.useFuzzy.actions
settingAnchor: "launcher-actions"
text: qsTr("Actions") text: qsTr("Actions")
onToggled: Config.launcher.useFuzzy.actions = checked onToggled: Config.launcher.useFuzzy.actions = checked
} }
ToggleRow {
checked: Config.launcher.useFuzzy.schemes
text: qsTr("Schemes")
onToggled: Config.launcher.useFuzzy.schemes = checked
}
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.variants checked: Config.launcher.useFuzzy.variants
settingAnchor: "launcher-variants"
text: qsTr("Variants") text: qsTr("Variants")
onToggled: Config.launcher.useFuzzy.variants = checked onToggled: Config.launcher.useFuzzy.variants = checked
@@ -123,6 +124,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.wallpapers checked: Config.launcher.useFuzzy.wallpapers
last: true last: true
settingAnchor: "launcher-wallpapers"
text: qsTr("Wallpapers") text: qsTr("Wallpapers")
onToggled: Config.launcher.useFuzzy.wallpapers = checked onToggled: Config.launcher.useFuzzy.wallpapers = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.performance.enabled checked: Config.dashboard.performance.enabled
first: true first: true
last: true last: true
settingAnchor: "resources-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.dashboard.performance.enabled = checked onToggled: Config.dashboard.performance.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showBattery checked: Config.dashboard.performance.showBattery
first: true first: true
settingAnchor: "resources-battery"
text: qsTr("Battery") text: qsTr("Battery")
onToggled: Config.dashboard.performance.showBattery = checked onToggled: Config.dashboard.performance.showBattery = checked
@@ -48,6 +50,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showGpu checked: Config.dashboard.performance.showGpu
settingAnchor: "resources-gpu"
text: qsTr("GPU") text: qsTr("GPU")
onToggled: Config.dashboard.performance.showGpu = checked onToggled: Config.dashboard.performance.showGpu = checked
@@ -55,6 +58,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showCpu checked: Config.dashboard.performance.showCpu
settingAnchor: "resources-cpu"
text: qsTr("CPU") text: qsTr("CPU")
onToggled: Config.dashboard.performance.showCpu = checked onToggled: Config.dashboard.performance.showCpu = checked
@@ -62,6 +66,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showMemory checked: Config.dashboard.performance.showMemory
settingAnchor: "resources-memory"
text: qsTr("Memory") text: qsTr("Memory")
onToggled: Config.dashboard.performance.showMemory = checked onToggled: Config.dashboard.performance.showMemory = checked
@@ -69,6 +74,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showStorage checked: Config.dashboard.performance.showStorage
settingAnchor: "resources-storage"
text: qsTr("Storage") text: qsTr("Storage")
onToggled: Config.dashboard.performance.showStorage = checked onToggled: Config.dashboard.performance.showStorage = checked
@@ -77,6 +83,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showNetwork checked: Config.dashboard.performance.showNetwork
last: true last: true
settingAnchor: "resources-network"
text: qsTr("Network") text: qsTr("Network")
onToggled: Config.dashboard.performance.showNetwork = checked onToggled: Config.dashboard.performance.showNetwork = checked
@@ -26,6 +26,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.sidebar.enabled checked: Config.sidebar.enabled
first: true first: true
settingAnchor: "sidebar-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.sidebar.enabled = checked onToggled: Config.sidebar.enabled = checked
@@ -34,6 +35,7 @@ PageBase {
SpinRow { SpinRow {
from: 0 from: 0
last: true last: true
settingAnchor: "sidebar-drag-threshold"
stepSize: 5 stepSize: 5
subtext: qsTr("Pixels dragged before the sidebar opens") subtext: qsTr("Pixels dragged before the sidebar opens")
text: qsTr("Drag threshold") text: qsTr("Drag threshold")
+5
View File
@@ -16,6 +16,7 @@ PageBase {
NavRow { NavRow {
first: true first: true
icon: "dock_to_bottom" icon: "dock_to_bottom"
settingAnchor: "panels-bar"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover") status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar") text: qsTr("Bar")
@@ -24,6 +25,7 @@ PageBase {
NavRow { NavRow {
icon: "dashboard" icon: "dashboard"
settingAnchor: "panels-dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard") text: qsTr("Dashboard")
@@ -32,6 +34,7 @@ PageBase {
NavRow { NavRow {
icon: "insert_chart" icon: "insert_chart"
settingAnchor: "panels-resources"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources") text: qsTr("Resources")
@@ -40,6 +43,7 @@ PageBase {
NavRow { NavRow {
icon: "apps" icon: "apps"
settingAnchor: "panels-launcher"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher") text: qsTr("Launcher")
@@ -49,6 +53,7 @@ PageBase {
NavRow { NavRow {
icon: "dock_to_right" icon: "dock_to_right"
last: true last: true
settingAnchor: "panels-sidebar"
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar") text: qsTr("Sidebar")
@@ -28,6 +28,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.screenshot.enable_pp checked: Config.screenshot.enable_pp
first: true first: true
settingAnchor: "screenshot-enable"
text: qsTr("Enable effects") text: qsTr("Enable effects")
onToggled: Config.screenshot.enable_pp = checked onToggled: Config.screenshot.enable_pp = checked
@@ -37,6 +38,7 @@ PageBase {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp enabled: Config.screenshot.enable_pp
last: true last: true
settingAnchor: "screenshot-mode"
subtext: qsTr("Automatic or manual effect values") subtext: qsTr("Automatic or manual effect values")
text: qsTr("Effects mode") text: qsTr("Effects mode")
@@ -67,6 +69,7 @@ PageBase {
checked: Config.screenshot.rounding checked: Config.screenshot.rounding
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true first: true
settingAnchor: "screenshot-enable-rounded-corners"
text: qsTr("Enable rounded corners") text: qsTr("Enable rounded corners")
onToggled: Config.screenshot.rounding = checked onToggled: Config.screenshot.rounding = checked
@@ -76,6 +79,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0 from: 0
last: true last: true
settingAnchor: "screenshot-corner-radius"
stepSize: 1 stepSize: 1
text: qsTr("Corner radius") text: qsTr("Corner radius")
to: 50 to: 50
@@ -95,6 +99,7 @@ PageBase {
checked: Config.screenshot.shadow checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true first: true
settingAnchor: "screenshot-enable-shadow"
text: qsTr("Enable shadow") text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked onToggled: Config.screenshot.shadow = checked
@@ -103,6 +108,7 @@ PageBase {
SpinRow { SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: 0 from: 0
settingAnchor: "screenshot-shadow-blur-amount"
stepSize: 1 stepSize: 1
text: qsTr("Shadow blur amount") text: qsTr("Shadow blur amount")
to: 100 to: 100
@@ -117,6 +123,7 @@ PageBase {
SpinRow { SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100 from: -100
settingAnchor: "screenshot-shadow-horizontal-offset"
stepSize: 10 stepSize: 10
text: qsTr("Shadow horizontal offset") text: qsTr("Shadow horizontal offset")
to: 100 to: 100
@@ -132,6 +139,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100 from: -100
last: true last: true
settingAnchor: "screenshot-shadow-vertical-offset"
stepSize: 10 stepSize: 10
text: qsTr("Shadow vertical offset") text: qsTr("Shadow vertical offset")
to: 100 to: 100
@@ -25,6 +25,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.showInFullscreen checked: Config.notifs.showInFullscreen
first: true first: true
settingAnchor: "notif-show-in-fullscreen"
subtext: qsTr("Whether notifications appear over fullscreen apps") subtext: qsTr("Whether notifications appear over fullscreen apps")
text: qsTr("Show in fullscreen") text: qsTr("Show in fullscreen")
@@ -33,6 +34,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.expire checked: Config.notifs.expire
settingAnchor: "notif-expire-automatically"
subtext: qsTr("Dismiss notifications after their timeout") subtext: qsTr("Dismiss notifications after their timeout")
text: qsTr("Expire automatically") text: qsTr("Expire automatically")
@@ -41,6 +43,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.openExpanded checked: Config.notifs.openExpanded
settingAnchor: "notif-open-expanded"
subtext: qsTr("Show notifications expanded by default") subtext: qsTr("Show notifications expanded by default")
text: qsTr("Open expanded") text: qsTr("Open expanded")
@@ -49,6 +52,7 @@ PageBase {
SpinRow { SpinRow {
from: 1000 from: 1000
settingAnchor: "notif-default-timeout"
stepSize: 500 stepSize: 500
subtext: qsTr("Time before a notification dismisses (ms)") subtext: qsTr("Time before a notification dismisses (ms)")
text: qsTr("Default timeout") text: qsTr("Default timeout")
@@ -61,6 +65,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
last: true last: true
settingAnchor: "notif-group-preview-count"
stepSize: 1 stepSize: 1
subtext: qsTr("Notifications shown per group before collapsing") subtext: qsTr("Notifications shown per group before collapsing")
text: qsTr("Group preview count") text: qsTr("Group preview count")
@@ -78,6 +83,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "notif-visible-toasts"
stepSize: 1 stepSize: 1
subtext: qsTr("Maximum number of toasts shown at once") subtext: qsTr("Maximum number of toasts shown at once")
text: qsTr("Visible toasts") text: qsTr("Visible toasts")
@@ -89,6 +95,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.chargingChanged checked: Config.utilities.toasts.chargingChanged
settingAnchor: "notif-charging-changes"
text: qsTr("Charging changes") text: qsTr("Charging changes")
onToggled: Config.utilities.toasts.chargingChanged = checked onToggled: Config.utilities.toasts.chargingChanged = checked
@@ -96,6 +103,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.gameModeChanged checked: Config.utilities.toasts.gameModeChanged
settingAnchor: "notif-game-mode-changes"
text: qsTr("Game mode changes") text: qsTr("Game mode changes")
onToggled: Config.utilities.toasts.gameModeChanged = checked onToggled: Config.utilities.toasts.gameModeChanged = checked
@@ -110,6 +118,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.audioOutputChanged checked: Config.utilities.toasts.audioOutputChanged
settingAnchor: "notif-audio-output-changes"
text: qsTr("Audio output changes") text: qsTr("Audio output changes")
onToggled: Config.utilities.toasts.audioOutputChanged = checked onToggled: Config.utilities.toasts.audioOutputChanged = checked
@@ -118,6 +127,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.audioInputChanged checked: Config.utilities.toasts.audioInputChanged
last: true last: true
settingAnchor: "notif-audio-input-changes"
text: qsTr("Audio input changes") text: qsTr("Audio input changes")
onToggled: Config.utilities.toasts.audioInputChanged = checked onToggled: Config.utilities.toasts.audioInputChanged = checked
+11
View File
@@ -71,6 +71,7 @@ PageBase {
first: true first: true
icon: "notifications" icon: "notifications"
last: true last: true
settingAnchor: "services-notifications"
status: qsTr("Notifications, toasts, timeouts") status: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications") text: qsTr("Notifications")
@@ -85,6 +86,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 100 from: 100
settingAnchor: "services-media-refresh"
stepSize: 50 stepSize: 50
subtext: qsTr("How often the media position updates (ms)") subtext: qsTr("How often the media position updates (ms)")
text: qsTr("Media refresh") text: qsTr("Media refresh")
@@ -97,6 +99,7 @@ PageBase {
SpinRow { SpinRow {
from: 0.5 from: 0.5
last: true last: true
settingAnchor: "services-system-stats-refresh"
stepSize: 0.5 stepSize: 0.5
subtext: qsTr("CPU, memory and GPU update interval (seconds)") subtext: qsTr("CPU, memory and GPU update interval (seconds)")
text: qsTr("System stats refresh") text: qsTr("System stats refresh")
@@ -118,6 +121,7 @@ PageBase {
first: true first: true
last: true last: true
menuItems: playerVariants.instances menuItems: playerVariants.instances
settingAnchor: "services-default-player"
subtext: qsTr("Preferred media player when several are open") subtext: qsTr("Preferred media player when several are open")
text: qsTr("Default player") text: qsTr("Default player")
@@ -132,6 +136,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "services-volume-step"
stepSize: 1 stepSize: 1
subtext: qsTr("Amount the volume changes per input (%)") subtext: qsTr("Amount the volume changes per input (%)")
text: qsTr("Volume step") text: qsTr("Volume step")
@@ -143,6 +148,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "services-brightness-step"
stepSize: 1 stepSize: 1
subtext: qsTr("Amount the brightness changes per input (%)") subtext: qsTr("Amount the brightness changes per input (%)")
text: qsTr("Brightness step") text: qsTr("Brightness step")
@@ -154,6 +160,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "services-brightness-minimum"
stepSize: 1 stepSize: 1
subtext: qsTr("Lowest allowed brightness (%)") subtext: qsTr("Lowest allowed brightness (%)")
text: qsTr("Brightness minimum") text: qsTr("Brightness minimum")
@@ -166,6 +173,7 @@ PageBase {
SpinRow { SpinRow {
from: 50 from: 50
last: true last: true
settingAnchor: "services-max-volume"
stepSize: 5 stepSize: 5
subtext: qsTr("Upper limit for output volume (%)") subtext: qsTr("Upper limit for output volume (%)")
text: qsTr("Max volume") text: qsTr("Max volume")
@@ -183,6 +191,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 10 from: 10
settingAnchor: "services-visualizer-resolution"
stepSize: 2 stepSize: 2
subtext: qsTr("Resolution of the audio visualizer") subtext: qsTr("Resolution of the audio visualizer")
text: qsTr("Visualizer resolution") text: qsTr("Visualizer resolution")
@@ -194,6 +203,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.general.color.smart checked: Config.general.color.smart
settingAnchor: "services-smart-color-scheme"
subtext: qsTr("Derive theme mode from the wallpaper") subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme") text: qsTr("Smart color scheme")
@@ -205,6 +215,7 @@ PageBase {
last: true last: true
menuItems: root.gpuItems menuItems: root.gpuItems
menuOnTop: true menuOnTop: true
settingAnchor: "services-gpu"
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type") subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
text: qsTr("GPU") text: qsTr("GPU")
+5
View File
@@ -49,6 +49,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.background.enabled checked: Config.background.enabled
first: true first: true
settingAnchor: "style-display-wallpaper"
text: qsTr("Display wallpaper") text: qsTr("Display wallpaper")
onToggled: Config.background.enabled = checked onToggled: Config.background.enabled = checked
@@ -57,6 +58,7 @@ PageBase {
ToggleRow { ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: DynamicColors.transparency.enabled checked: DynamicColors.transparency.enabled
settingAnchor: "style-transparency"
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers) subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
text: qsTr("Transparency") text: qsTr("Transparency")
@@ -67,6 +69,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: !DynamicColors.light checked: !DynamicColors.light
last: true last: true
settingAnchor: "style-dark-theme"
text: qsTr("Dark theme") text: qsTr("Dark theme")
onToggled: DynamicColors.setMode(checked ? "dark" : "light") onToggled: DynamicColors.setMode(checked ? "dark" : "light")
@@ -88,6 +91,7 @@ PageBase {
checked: Config.general.color.scheduleDark checked: Config.general.color.scheduleDark
first: true first: true
settingAnchor: "style-schedule-dark-mode"
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode") text: qsTr("Schedule dark mode")
@@ -129,6 +133,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset checked: Config.general.color.scheduleHyprsunset
last: true last: true
settingAnchor: "style-schedule-hyprsunset"
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset") text: qsTr("Schedule hyprsunset")
+155
View File
@@ -0,0 +1,155 @@
pragma Singleton
import "../../scripts/fzf.js" as Fzf
import QtQuick
import Quickshell
import ZShell
import qs.Config
Singleton {
id: root
property var fzfFinder: null
property var inverted: ({})
property var ranking: ({})
function highlight(text: string, search: string, colour: color): string {
const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const tokens = tokenize(search);
if (tokens.length === 0)
return escaped;
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
const pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
}
function lookup(token: string): var {
const result = ({});
const exact = root.inverted[token] !== undefined;
const keys = exact ? [token] : Object.keys(root.inverted).filter(k => k.startsWith(token));
for (const key of keys) {
const rank = root.ranking[key] ?? ({});
for (const id of root.inverted[key]) {
const w = rank[id] ?? 0.1;
if (result[id] === undefined || w > result[id])
result[id] = w;
}
}
return result;
}
function query(search: string): list<QtObject> {
const tokens = root.tokenize(search);
if (tokens.length === 0)
return [];
const scores = ({});
const hitCounts = ({});
for (const token of tokens) {
const matches = root.lookup(token);
for (const id in matches) {
scores[id] = (scores[id] ?? 0) + matches[id];
hitCounts[id] = (hitCounts[id] ?? 0) + 1;
}
}
const ranked = Object.keys(scores).filter(id => hitCounts[id] === tokens.length).sort((a, b) => scores[b] - scores[a] || (parseInt(a) - parseInt(b))).slice(0, 25);
const all = entries.instances;
const out = ranked.map(id => all[parseInt(id)]).filter(e => e !== undefined);
if (out.length < 5 && root.fzfFinder) {
const seen = ({});
for (const id of ranked)
seen[id] = true;
const fuzzy = root.fzfFinder.find(search);
for (const r of fuzzy) {
const idx = r.item.idx;
if (seen[idx])
continue;
seen[idx] = true;
const entry = all[idx];
if (entry !== undefined)
out.push(entry);
if (out.length >= 25)
break;
}
}
return out;
}
function tokenize(text: string): var {
return text.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 0);
}
Component.onCompleted: {
try {
const data = JSON.parse(ZUtils.settingsIndex());
entries.model = data.entries;
root.inverted = data.inverted ?? {};
root.ranking = data.ranking ?? {};
const docs = data.entries.map((e, i) => ({
idx: i,
text: e.title
}));
root.fzfFinder = new Fzf.Finder(docs, {
selector: d => d.text,
limit: 25
});
} catch (e) {
entries.model = [];
root.inverted = {};
root.ranking = {};
root.fzfFinder = null;
}
}
Variants {
id: entries
SettingEntry {
}
}
component SettingEntry: QtObject {
readonly property string anchor: modelData.anchor ?? ""
readonly property var crumbIcons: modelData.crumbIcons
readonly property var crumbLabels: modelData.crumbLabels
readonly property bool isToggle: togglePath.length > 0
required property var modelData
readonly property int pageIdx: modelData.pageIdx
readonly property string section: modelData.section ?? ""
readonly property var subPath: modelData.subPath
readonly property string subtext: modelData.subtext ?? ""
readonly property string title: modelData.title
readonly property string togglePath: modelData.togglePath ?? ""
readonly property bool toggleValue: {
if (!isToggle)
return false;
let obj = Config;
const parts = togglePath.split(".");
for (const part of parts) {
if (obj === undefined || obj === null)
return false;
obj = obj[part];
}
return obj ?? false;
}
function setToggle(value: bool): void {
if (!isToggle)
return;
const parts = togglePath.split(".");
let obj = Config;
for (let k = 0; k < parts.length - 1; k++) {
if (obj === undefined || obj === null)
return;
obj = obj[parts[k]];
}
if (obj !== undefined && obj !== null)
obj[parts[parts.length - 1]] = value;
}
}
}
+34 -1
View File
@@ -8,14 +8,19 @@ QtObject {
property bool animatingContainer property bool animatingContainer
property int currentPageIdx property int currentPageIdx
property bool isWindow property bool isWindow
property string lastAnchor
property list<int> pendingSubPath
property ShellScreen screen property ShellScreen screen
property string searchAnchor
property bool searchOpen property bool searchOpen
property string searchText
property DesktopEntry selectedApp property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice property BluetoothDevice selectedBtDevice
property string selectedWallpaperCategory property string selectedWallpaperCategory
property list<int> subPageIdxStack property list<int> subPageIdxStack
signal close signal close
signal highlightSetting(anchor: string)
signal subPageClosed signal subPageClosed
signal subPageOpened(idx: int) signal subPageOpened(idx: int)
@@ -24,10 +29,38 @@ QtObject {
subPageIdxStack.pop(); subPageIdxStack.pop();
} }
function jumpToSetting(pageIdx: int, subPath: var, anchor: string): void {
const samePage = currentPageIdx === pageIdx;
const sameSub = subPageIdxStack.length === subPath.length && subPath.every((v, i) => subPageIdxStack[i] === v);
if (samePage && sameSub && anchor === lastAnchor) {
highlightSetting(anchor);
return;
}
lastAnchor = anchor;
if (samePage && sameSub) {
searchAnchor = "";
searchAnchor = anchor;
return;
}
searchAnchor = anchor;
if (!samePage) {
pendingSubPath = subPath.slice();
currentPageIdx = pageIdx;
} else {
while (subPageIdxStack.length > 0)
closeSubPage();
for (let i = 0; i < subPath.length; i++)
openSubPage(subPath[i]);
}
}
function openSubPage(idx: int): void { function openSubPage(idx: int): void {
subPageIdxStack.push(idx); subPageIdxStack.push(idx);
subPageOpened(idx); subPageOpened(idx);
} }
onCurrentPageIdxChanged: subPageIdxStack.length = 0 onCurrentPageIdxChanged: {
subPageIdxStack = pendingSubPath;
pendingSubPath = [];
}
} }
+219
View File
@@ -0,0 +1,219 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
import qs.Helpers
CustomClippingRect {
id: root
required property var wrapper
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: networkPopContent.height + networks.implicitHeight + networkPopContent.anchors.margins + networks.anchors.margins * 2
implicitWidth: 500 + 8 * 2
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
ColumnLayout {
id: networkPopContent
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.top: parent.top
CustomText {
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.rightMargin: Appearance.padding.extraSmall
font.pointSize: Appearance.font.size.large
text: qsTr("Network")
}
Toggle {
Layout.preferredHeight: visible ? implicitHeight : 0
checked: Network.wifiEnabled
label: qsTr("WiFi enabled")
toggle.onToggled: Network.setWifi(checked)
}
CustomText {
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.rightMargin: Appearance.padding.extraSmall
Layout.topMargin: visible ? Appearance.spacing.small : 0
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("%1 networks available").arg(Network.networks.length) // qmllint disable missing-property
}
}
ColumnLayout {
id: networks
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.top: networkPopContent.bottom
Repeater {
model: ScriptModel {
values: [...Network.networks]
}
RowLayout {
id: networkItem
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.rightMargin: Appearance.padding.extraSmall
opacity: 0
scale: 0.7
spacing: Appearance.spacing.small
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: {
opacity = 1;
scale = 1;
}
MaterialIcon {
color: networkItem.modelData.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, Network.isSecure(networkItem.modelData.security))
}
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.extraSmall
Layout.rightMargin: Appearance.spacing.extraSmall
color: networkItem.modelData.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
elide: Text.ElideRight
text: networkItem.modelData.name
}
CustomRect {
color: Qt.alpha(DynamicColors.palette.m3primary, networkItem.modelData.active ? 1 : 0)
implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.extraSmall
implicitWidth: implicitHeight
radius: Appearance.rounding.full
// CircularIndicator {
// anchors.fill: parent
// running: networkItem.loading
// }
StateLayer {
color: networkItem.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
onClicked: {
console.log(Network.devices[1].scannerEnabled, Network.devices[2].scannerEnabled, Network.devices[3].scannerEnabled, Network.devices[4].scannerEnabled);
}
}
MaterialIcon {
id: wirelessConnectIcon
anchors.centerIn: parent
animate: true
color: networkItem.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
text: networkItem.modelData.active ? "link_off" : "link"
// opacity: networkItem.loading ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}
}
}
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Appearance.spacing.small : 0
color: DynamicColors.palette.m3primaryContainer
implicitHeight: rescanBtn.implicitHeight + Appearance.padding.small
radius: Appearance.rounding.full
StateLayer {
color: DynamicColors.palette.m3onPrimaryContainer
enabled: !Network.scanning
onClicked: Network.rescanWifi()
}
RowLayout {
id: rescanBtn
anchors.centerIn: parent
opacity: Network.scanning ? 0 : 1
spacing: Appearance.spacing.small
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MaterialIcon {
id: scanIcon
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
animate: true
color: DynamicColors.palette.m3onPrimaryContainer
text: "wifi_find"
}
CustomText {
Layout.topMargin: -Math.round(scanIcon.fontInfo.pointSize * 0.0575)
color: DynamicColors.palette.m3onPrimaryContainer
text: qsTr("Rescan networks")
}
}
CircularIndicator {
anchors.centerIn: parent
bgColor: "transparent"
implicitSize: parent.implicitHeight - Appearance.padding.large
running: Network.scanning
strokeWidth: Appearance.padding.extraSmall / 2
}
}
}
component Toggle: RowLayout {
property alias checked: toggle.checked
required property string label
property alias toggle: toggle
Layout.fillWidth: true
Layout.rightMargin: Appearance.padding.extraSmall
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
text: parent.label
}
CustomSwitch {
id: toggle
}
}
}
+9
View File
@@ -52,6 +52,11 @@ RowLayout {
id: "audio", id: "audio",
item: child item: child
}; };
if (child.objectName === "networkWidget" && Config.bar.popouts.network)
return {
id: "network",
item: child
};
if (child.objectName === "upowerWidget" && Config.bar.popouts.upower) if (child.objectName === "upowerWidget" && Config.bar.popouts.upower)
return { return {
id: "upower", id: "upower",
@@ -146,6 +151,10 @@ RowLayout {
} }
} }
NetworkWidget {
objectName: "networkWidget"
}
UPowerWidget { UPowerWidget {
Layout.fillHeight: true Layout.fillHeight: true
objectName: "upowerWidget" objectName: "upowerWidget"
+24
View File
@@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Daemons
import qs.Modules
import qs.Config
import qs.Components
RowLayout {
id: root
// property color barColor: DynamicColors.palette.m3primary
property color textColor: DynamicColors.palette.m3onSurface
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: root.textColor // Network.connected ? root.textColor : DynamicColors.palette.m3error
fill: 1
font.pointSize: Appearance.font.size.larger
text: "android_wifi_4_bar"
}
}
+1 -2
View File
@@ -26,8 +26,7 @@ void BlobGroup::setColor(const QColor& c) {
} }
void BlobGroup::setCornerFill(bool e) { void BlobGroup::setCornerFill(bool e) {
if (m_cornerFill == e) if (m_cornerFill == e) return;
return;
m_cornerFill = e; m_cornerFill = e;
emit cornerFillChanged(); emit cornerFillChanged();
markDirty(); markDirty();
+24 -22
View File
@@ -9,11 +9,15 @@ class BlobShape;
class BlobInvertedRect; class BlobInvertedRect;
class BlobGroup : public QObject { class BlobGroup : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged) Q_PROPERTY(
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged) smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(
bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY
cornerFillChanged)
public: public:
explicit BlobGroup(QObject* parent = nullptr); explicit BlobGroup(QObject* parent = nullptr);
@@ -27,14 +31,12 @@ Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFill
void setColor(const QColor& c); void setColor(const QColor& c);
[[nodiscard]] bool cornerFill() const { [[nodiscard]] bool cornerFill() const { return m_cornerFill; }
return m_cornerFill;
}
void setCornerFill(bool e); void setCornerFill(bool e);
void addShape(BlobShape* shape); void addShape(BlobShape* shape);
void removeShape(BlobShape* shape); void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect); void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect);
@@ -49,16 +51,16 @@ void removeShape(BlobShape* shape);
void markShapeDirty(BlobShape* source); void markShapeDirty(BlobShape* source);
void ensurePhysicsUpdated(); void ensurePhysicsUpdated();
signals: signals:
void smoothingChanged(); void smoothingChanged();
void colorChanged(); void colorChanged();
void cornerFillChanged(); void cornerFillChanged();
private: private:
qreal m_smoothing = 32.0; qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff }; QColor m_color{0x44, 0x88, 0xff};
bool m_cornerFill = true; bool m_cornerFill = true;
QList<BlobShape*> m_shapes; QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr; BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false; bool m_physicsUpdated = false;
}; };
+28 -29
View File
@@ -32,11 +32,9 @@ void BlobRect::updatePolish() {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
}, Qt::QueuedConnection);
Qt::QueuedConnection
);
} }
} else { } else {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
@@ -184,8 +182,7 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
bool BlobRect::isCornerExcluded(const BlobShape* other) const { bool BlobRect::isCornerExcluded(const BlobShape* other) const {
for (const auto& ptr : m_excludeCorners) { for (const auto& ptr : m_excludeCorners) {
if (ptr == other) if (ptr == other) return true;
return true;
} }
return false; return false;
} }
@@ -203,8 +200,15 @@ QQmlListProperty<BlobRect> BlobRect::exclude() {
} }
QQmlListProperty<BlobRect> BlobRect::excludeCorners() { QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt, return QQmlListProperty<BlobRect>(
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast); this,
nullptr,
&excludeCornersAppend,
&excludeCornersCount,
&excludeCornersAt,
&excludeCornersClear,
&excludeCornersReplace,
&excludeCornersRemoveLast);
} }
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) { void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
@@ -249,11 +253,11 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
emit self->excludeChanged(); emit self->excludeChanged();
} }
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) { void BlobRect::excludeCornersAppend(
QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners.append(rect); self->m_excludeCorners.append(rect);
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
@@ -262,36 +266,33 @@ qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
return self->m_excludeCorners.size(); return self->m_excludeCorners.size();
} }
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) { BlobRect* BlobRect::excludeCornersAt(
QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.at(index); return self->m_excludeCorners.at(index);
} }
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty()) if (self->m_excludeCorners.isEmpty()) return;
return;
self->m_excludeCorners.clear(); self->m_excludeCorners.clear();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) { void BlobRect::excludeCornersReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners[index] = rect; self->m_excludeCorners[index] = rect;
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty()) if (self->m_excludeCorners.isEmpty()) return;
return;
self->m_excludeCorners.removeLast(); self->m_excludeCorners.removeLast();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
@@ -319,11 +320,9 @@ void BlobRect::checkAtRest(float speed) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
}, Qt::QueuedConnection);
Qt::QueuedConnection
);
} }
} }
} }
+60 -41
View File
@@ -8,18 +8,32 @@
#include <qqmllist.h> #include <qqmllist.h>
class BlobRect : public BlobShape { class BlobRect : public BlobShape {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) Q_PROPERTY(
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) qreal stiffness READ stiffness WRITE setStiffness NOTIFY
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged) stiffnessChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged) Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged) Q_PROPERTY(
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged) deformScaleChanged)
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged) Q_PROPERTY(
Q_PROPERTY( QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) Q_PROPERTY(
QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY
excludeCornersChanged)
Q_PROPERTY(
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
topLeftRadiusChanged)
Q_PROPERTY(
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
topRightRadiusChanged)
Q_PROPERTY(
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
NOTIFY bottomLeftRadiusChanged)
Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
public: public:
explicit BlobRect(QQuickItem* parent = nullptr); explicit BlobRect(QQuickItem* parent = nullptr);
@@ -52,12 +66,12 @@ Q_PROPERTY(
} }
} }
QQmlListProperty<BlobRect> exclude(); QQmlListProperty<BlobRect> exclude();
QQmlListProperty<BlobRect> excludeCorners(); QQmlListProperty<BlobRect> excludeCorners();
bool isExcluded(const BlobShape* other) const override; bool isExcluded(const BlobShape* other) const override;
bool isCornerExcluded(const BlobShape* other) const override; bool isCornerExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override; void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; } [[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
@@ -77,16 +91,16 @@ void cornerRadii(float out[4]) const override;
void setBottomRightRadius(qreal r); void setBottomRightRadius(qreal r);
signals: signals:
void stiffnessChanged(); void stiffnessChanged();
void dampingChanged(); void dampingChanged();
void deformScaleChanged(); void deformScaleChanged();
void excludeChanged(); void excludeChanged();
void excludeCornersChanged(); void excludeCornersChanged();
void topLeftRadiusChanged(); void topLeftRadiusChanged();
void topRightRadiusChanged(); void topRightRadiusChanged();
void bottomLeftRadiusChanged(); void bottomLeftRadiusChanged();
void bottomRightRadiusChanged(); void bottomRightRadiusChanged();
protected: protected:
void updatePolish() override; void updatePolish() override;
@@ -121,20 +135,25 @@ void bottomRightRadiusChanged();
qreal m_bottomLeftRadius = -1; qreal m_bottomLeftRadius = -1;
qreal m_bottomRightRadius = -1; qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect> > m_exclude; QList<QPointer<BlobRect>> m_exclude;
QList<QPointer<BlobRect> > m_excludeCorners; QList<QPointer<BlobRect>> m_excludeCorners;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect); static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop); static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index); static BlobRect* excludeAt(
static void excludeClear(QQmlListProperty<BlobRect>* prop); QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect); static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop); static void excludeReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect); static void excludeCornersAppend(
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop); QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index); static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop); static BlobRect* excludeCornersAt(
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect); QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop); static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
static void excludeCornersReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
}; };
+4 -8
View File
@@ -38,8 +38,7 @@ static float cornerFillFactor(float sd, float smoothFactor) {
return std::max(outside, inside); return std::max(outside, inside);
} }
BlobShape::BlobShape(QQuickItem* parent) BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
: QQuickItem(parent) {
setFlag(ItemHasContents); setFlag(ItemHasContents);
} }
@@ -312,13 +311,10 @@ void BlobShape::updatePolish() {
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh; const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) { for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
if (j == i) if (j == i) continue;
continue; if (riExcludeMask & (1 << j)) continue;
if (riExcludeMask & (1 << j))
continue;
BlobShape* const sj = rectShapes[j]; BlobShape* const sj = rectShapes[j];
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) continue;
continue;
const auto& rj = m_cachedRects[j]; const auto& rj = m_cachedRects[j];
const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh); const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh);
const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh); const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh);
+24 -24
View File
@@ -55,11 +55,11 @@ class BlobShape : public QQuickItem {
virtual bool isExcluded(const BlobShape*) const { return false; } virtual bool isExcluded(const BlobShape*) const { return false; }
virtual bool isCornerExcluded(const BlobShape* /*other*/) const { virtual bool isCornerExcluded(const BlobShape* /*other*/) const {
return false; return false;
} }
virtual void cornerRadii(float out[4]) const; virtual void cornerRadii(float out[4]) const;
virtual void updatePhysics() {} virtual void updatePhysics() {}
@@ -67,25 +67,25 @@ virtual void cornerRadii(float out[4]) const;
virtual void unregisterFromGroup(); virtual void unregisterFromGroup();
void updateCenteredDeformMatrix(); void updateCenteredDeformMatrix();
BlobGroup* m_group = nullptr; BlobGroup* m_group = nullptr;
qreal m_radius = 0; qreal m_radius = 0;
QMatrix4x4 m_deformMatrix; // identity by default QMatrix4x4 m_deformMatrix; // identity by default
QMatrix4x4 m_centeredDeformMatrix; QMatrix4x4 m_centeredDeformMatrix;
// Cached data from updatePolish // Cached data from updatePolish
float m_cachedPaddedX = 0; float m_cachedPaddedX = 0;
float m_cachedPaddedY = 0; float m_cachedPaddedY = 0;
float m_cachedPaddedW = 0; float m_cachedPaddedW = 0;
float m_cachedPaddedH = 0; float m_cachedPaddedH = 0;
float m_pendingDw = 0; float m_pendingDw = 0;
float m_pendingDh = 0; float m_pendingDh = 0;
QRectF m_localPaddedRect; QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
bool m_cachedHasInverted = false; bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
}; };
+6 -1
View File
@@ -24,11 +24,12 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
qt_standard_project_setup(REQUIRES 6.9) qt_standard_project_setup(REQUIRES 6.9)
function(qml_module arg_TARGET) function(qml_module arg_TARGET)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES") cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES;RESOURCES")
qt_add_qml_module(${arg_TARGET} qt_add_qml_module(${arg_TARGET}
URI ${arg_URI} URI ${arg_URI}
VERSION 1.0 VERSION 1.0
SOURCES ${arg_SOURCES} SOURCES ${arg_SOURCES}
RESOURCES ${arg_RESOURCES}
) )
qt_query_qml_module(${arg_TARGET} qt_query_qml_module(${arg_TARGET}
@@ -47,6 +48,8 @@ function(qml_module arg_TARGET)
target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES}) target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES})
endfunction() endfunction()
set_source_files_properties("${SETTINGS_INDEX_JSON}" PROPERTIES QT_RESOURCE_ALIAS "settings-index.json")
qml_module(ZShell qml_module(ZShell
URI ZShell URI ZShell
SOURCES SOURCES
@@ -57,6 +60,8 @@ qml_module(ZShell
toaster.hpp toaster.cpp toaster.hpp toaster.cpp
qalculator.hpp qalculator.cpp qalculator.hpp qalculator.cpp
zutils.hpp zutils.cpp zutils.hpp zutils.cpp
RESOURCES
"${SETTINGS_INDEX_JSON}"
LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::Quick Qt::Quick
+39 -26
View File
@@ -10,23 +10,28 @@ Q_LOGGING_CATEGORY(lcAppDb, "ZShell.appdb", QtInfoMsg)
namespace ZShell { namespace ZShell {
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent) AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
: QObject(parent) : QObject(parent), m_entry(entry), m_frequency(frequency) {
, m_entry(entry)
, m_frequency(frequency) {
const auto mo = m_entry->metaObject(); const auto mo = m_entry->metaObject();
const auto tmo = &AppEntry::staticMetaObject; const auto tmo = &AppEntry::staticMetaObject;
for (const auto& prop : for (const auto& prop :
{ "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) { {"name",
"comment",
"execString",
"startupClass",
"genericName",
"categories",
"keywords"}) {
const auto metaProp = mo->property(mo->indexOfProperty(prop)); const auto metaProp = mo->property(mo->indexOfProperty(prop));
const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop)); const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop));
QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal()); QObject::connect(
m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal());
} }
QObject::connect(m_entry, &QObject::destroyed, this, [this]() { QObject::connect(m_entry, &QObject::destroyed, this, [this]() {
m_entry = nullptr; m_entry = nullptr;
deleteLater(); deleteLater();
}); });
} }
QObject* AppEntry::entry() const { QObject* AppEntry::entry() const {
@@ -118,7 +123,9 @@ AppDb::AppDb(QObject* parent)
db.open(); db.open();
QSqlQuery query(db); QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); query.exec(
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
"frequency INTEGER)");
} }
QString AppDb::uuid() const { QString AppDb::uuid() const {
@@ -145,7 +152,9 @@ void AppDb::setPath(const QString& path) {
db.open(); db.open();
QSqlQuery query(db); QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); query.exec(
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
"frequency INTEGER)");
updateAppFrequencies(); updateAppFrequencies();
} }
@@ -183,7 +192,9 @@ void AppDb::setFavoriteApps(const QStringList& favApps) {
if (re.isValid()) { if (re.isValid()) {
m_favoriteAppsRegex << re; m_favoriteAppsRegex << re;
} else { } else {
qCWarning(lcAppDb) << "setFavoriteApps: regular expression is not valid:" << re.pattern(); qCWarning(lcAppDb)
<< "setFavoriteApps: regular expression is not valid:"
<< re.pattern();
} }
} }
@@ -191,8 +202,7 @@ void AppDb::setFavoriteApps(const QStringList& favApps) {
} }
QString AppDb::regexifyString(const QString& original) const { QString AppDb::regexifyString(const QString& original) const {
if (original.startsWith('^') && original.endsWith('$')) if (original.startsWith('^') && original.endsWith('$')) return original;
return original;
const QString escaped = QRegularExpression::escape(original); const QString escaped = QRegularExpression::escape(original);
return QStringLiteral("^%1$").arg(escaped); return QStringLiteral("^%1$").arg(escaped);
@@ -206,9 +216,10 @@ void AppDb::incrementFrequency(const QString& id) {
auto db = QSqlDatabase::database(m_uuid); auto db = QSqlDatabase::database(m_uuid);
QSqlQuery query(db); QSqlQuery query(db);
query.prepare("INSERT INTO frequencies (id, frequency) " query.prepare(
"VALUES (:id, 1) " "INSERT INTO frequencies (id, frequency) "
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1"); "VALUES (:id, 1) "
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
query.bindValue(":id", id); query.bindValue(":id", id);
query.exec(); query.exec();
@@ -221,7 +232,8 @@ void AppDb::incrementFrequency(const QString& id) {
emit appsChanged(); emit appsChanged();
} }
} else { } else {
qCWarning(lcAppDb) << "incrementFrequency: could not find app with id" << id; qCWarning(lcAppDb) << "incrementFrequency: could not find app with id"
<< id;
} }
} }
@@ -232,15 +244,16 @@ QList<AppEntry*>& AppDb::getSortedApps() const {
QSet<QString> favSet; QSet<QString> favSet;
favSet.reserve(m_sortedApps.size()); favSet.reserve(m_sortedApps.size());
for (const auto* app : std::as_const(m_sortedApps)) { for (const auto* app : std::as_const(m_sortedApps)) {
if (isFavorite(app)) if (isFavorite(app)) favSet.insert(app->id());
favSet.insert(app->id());
} }
std::sort(m_sortedApps.begin(), m_sortedApps.end(), [&favSet](AppEntry* a, AppEntry* b) { std::sort(
m_sortedApps.begin(),
m_sortedApps.end(),
[&favSet](AppEntry* a, AppEntry* b) {
const bool aIsFav = favSet.contains(a->id()); const bool aIsFav = favSet.contains(a->id());
const bool bIsFav = favSet.contains(b->id()); const bool bIsFav = favSet.contains(b->id());
if (aIsFav != bIsFav) if (aIsFav != bIsFav) return aIsFav;
return aIsFav;
if (a->frequency() != b->frequency()) if (a->frequency() != b->frequency())
return a->frequency() > b->frequency(); return a->frequency() > b->frequency();
return a->name().localeAwareCompare(b->name()) < 0; return a->name().localeAwareCompare(b->name()) < 0;
@@ -293,10 +306,10 @@ void AppDb::updateApps() {
dirty = true; dirty = true;
auto* const newEntry = new AppEntry(entry, getFrequency(id), this); auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() { QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
if (m_apps.remove(id)) { if (m_apps.remove(id)) {
emit appsChanged(); emit appsChanged();
} }
}); });
m_apps.insert(id, newEntry); m_apps.insert(id, newEntry);
} }
} }
+31 -26
View File
@@ -65,11 +65,16 @@ class AppDb : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString uuid READ uuid CONSTANT) Q_PROPERTY(QString uuid READ uuid CONSTANT)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED) Q_PROPERTY(
Q_PROPERTY(QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY favoriteAppsChanged REQUIRED) QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged
Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged) REQUIRED)
Q_PROPERTY(
QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY
favoriteAppsChanged REQUIRED)
Q_PROPERTY(
QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
public: public:
explicit AppDb(QObject* parent = nullptr); explicit AppDb(QObject* parent = nullptr);
@@ -82,36 +87,36 @@ Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
[[nodiscard]] QObjectList entries() const; [[nodiscard]] QObjectList entries() const;
void setEntries(const QObjectList& entries); void setEntries(const QObjectList& entries);
[[nodiscard]] QStringList favoriteApps() const; [[nodiscard]] QStringList favoriteApps() const;
void setFavoriteApps(const QStringList& favApps); void setFavoriteApps(const QStringList& favApps);
[[nodiscard]] QQmlListProperty<AppEntry> apps(); [[nodiscard]] QQmlListProperty<AppEntry> apps();
Q_INVOKABLE void incrementFrequency(const QString& id); Q_INVOKABLE void incrementFrequency(const QString& id);
signals: signals:
void pathChanged(); void pathChanged();
void entriesChanged(); void entriesChanged();
void favoriteAppsChanged(); void favoriteAppsChanged();
void appsChanged(); void appsChanged();
private: private:
QTimer* m_timer; QTimer* m_timer;
const QString m_uuid; const QString m_uuid;
QString m_path; QString m_path;
QObjectList m_entries; QObjectList m_entries;
QStringList m_favoriteApps; QStringList m_favoriteApps;
QList<QRegularExpression> m_favoriteAppsRegex; QList<QRegularExpression> m_favoriteAppsRegex;
QHash<QString, AppEntry*> m_apps; QHash<QString, AppEntry*> m_apps;
mutable QList<AppEntry*> m_sortedApps; mutable QList<AppEntry*> m_sortedApps;
QString regexifyString(const QString& original) const; QString regexifyString(const QString& original) const;
QList<AppEntry*>& getSortedApps() const; QList<AppEntry*>& getSortedApps() const;
bool isFavorite(const AppEntry* app) const; bool isFavorite(const AppEntry* app) const;
quint32 getFrequency(const QString& id) const; quint32 getFrequency(const QString& id) const;
void updateAppFrequencies(); void updateAppFrequencies();
void updateApps(); void updateApps();
}; };
} // namespace ZShell } // namespace ZShell
+10
View File
@@ -10,6 +10,7 @@
#include <qjsprimitivevalue.h> #include <qjsprimitivevalue.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qfile.h>
Q_LOGGING_CATEGORY(lcZUtils, "ZShell.cutils", QtInfoMsg) Q_LOGGING_CATEGORY(lcZUtils, "ZShell.cutils", QtInfoMsg)
@@ -170,6 +171,15 @@ qreal ZUtils::clamp(qreal value, qreal min, qreal max) {
return qBound(min, value, max); return qBound(min, value, max);
} }
QString ZUtils::settingsIndex() {
QFile file(QStringLiteral(":/qt/qml/ZShell/settings-index.json"));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(lcZUtils) << "Failed to open embedded settings index";
return QString();
}
return QString::fromUtf8(file.readAll());
}
#ifndef ZSHELL_VERSION #ifndef ZSHELL_VERSION
#define ZSHELL_VERSION "" #define ZSHELL_VERSION ""
#endif #endif
+13 -11
View File
@@ -12,18 +12,18 @@ class ZUtils : public QObject {
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString version READ version CONSTANT)
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT) Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
public: public:
// clang-format off // clang-format off
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
// clang-format on // clang-format on
Q_INVOKABLE static bool copyFile( Q_INVOKABLE static bool copyFile(
const QUrl& source, const QUrl& target, bool overwrite = true); const QUrl& source, const QUrl& target, bool overwrite = true);
@@ -32,6 +32,8 @@ Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rec
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max); Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
Q_INVOKABLE static QString settingsIndex();
[[nodiscard]] QString version() const; [[nodiscard]] QString version() const;
[[nodiscard]] QString qtVersion() const; [[nodiscard]] QString qtVersion() const;
}; };
File diff suppressed because it is too large Load Diff
+407
View File
@@ -0,0 +1,407 @@
from __future__ import annotations
import json
import re
import sys
from collections import defaultdict
from functools import lru_cache
from pathlib import Path
@lru_cache(maxsize=None)
def read_lines(path: Path) -> tuple[str, ...]:
return tuple(path.read_text().splitlines())
ROW_RE = re.compile(
r'^\s*(ToggleRow|SliderRow|SelectRow|SpinRow|NavRow|InfoRow|PopupRow|OverlayRow|DefaultRow)\s*\{')
LABEL_RE = re.compile(r'^\s*(?:label|text):\s*qsTr\("([^"]+)"\)')
ANCHOR_RE = re.compile(r'^\s*settingAnchor:\s*"([^"]+)"')
CHECKED_RE = re.compile(r'^\s*checked:\s*(?:Config)\.([\w.]+)\s*$')
ONTOGGLED_RE = re.compile(
r'^\s*onToggled:\s*(?:Config)\.([\w.]+)\s*=\s*checked\s*$')
ICON_RE = re.compile(r'^\s*icon:\s*"([^"]+)"')
SKIP_LABELS = {"Muted", "None"}
FIELD_WEIGHT = {"title": 1.0, "keywords": 0.4}
STOPWORDS = {"the", "a", "an", "of", "and", "or", "to", "on", "in", "for"}
def find_pages_dir(settings: Path) -> Path:
return settings / "Pages"
def discover_files(settings: Path) -> dict[str, Path]:
files: dict[str, Path] = {}
for p in find_pages_dir(settings).rglob("*.qml"):
files[p.stem] = p
return files
PAGE_NAME_RE = re.compile(r'^\s*name:\s*qsTr\("([^"]+)"\)')
PAGE_ICON_RE = re.compile(r'^\s*icon:\s*"([^"]+)"')
def parse_page_registry(settings: Path) -> list[tuple[str, str]]:
text = (settings / "PageRegistry.qml").read_text().splitlines()
start = next(
i for i, line in enumerate(text)
if re.search(r'\bpages\s*:\s*\[', line)
)
out: list[tuple[str, str]] = []
i = start + 1
while i < len(text):
line = text[i].strip()
if line.startswith("]"):
break
if line.startswith("//") or not line:
i += 1
continue
if line.startswith("{"):
name = None
icon = None
i += 1
while i < len(text):
s = text[i].strip()
if s.startswith("}"):
if name is not None:
out.append((icon or "tune", name))
break
if name is None:
m = PAGE_NAME_RE.match(text[i])
if m:
name = m.group(1)
if icon is None:
mi = PAGE_ICON_RE.match(text[i])
if mi:
icon = mi.group(1)
i += 1
i += 1
return out
BLOCK_RE = re.compile(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*$')
def _strip_comment(line: str) -> str:
return line.split("//", 1)[0].rstrip()
def parse_block(lines: list[str], i: int) -> tuple[str, list[tuple[str, list]], int]:
line = _strip_comment(lines[i]).strip()
m = BLOCK_RE.match(line)
if not m:
raise ValueError(f"Expected block start at line {i + 1}: {lines[i]!r}")
name = m.group(1)
i += 1
children: list[tuple[str, list]] = []
while i < len(lines):
s = _strip_comment(lines[i]).strip()
if not s:
i += 1
continue
if s.startswith("}"):
return name, children, i + 1
if BLOCK_RE.match(s):
child_name, child_children, i = parse_block(lines, i)
children.append((child_name, child_children))
continue
i += 1
raise ValueError(f"Unterminated block: {name}")
def collect_page_names(block: tuple[str, list[tuple[str, list]]]) -> list[str]:
name, children = block
if name != "Component":
return [name]
for child_name, child_children in children:
if child_name == "StackPage":
out: list[str] = []
for grand_name, grand_children in child_children:
if grand_name == "Component":
out.extend(collect_page_names((grand_name, grand_children)))
return out
if child_name != "Component":
return [child_name]
return []
def parse_page_comps(settings: Path) -> list[list[str]]:
text = (settings / "PageCompRegistry.qml").read_text().splitlines()
start = next(
i for i, line in enumerate(text)
if re.search(r'\bpageComps\s*:\s*\[', _strip_comment(line))
)
comps: list[list[str]] = []
i = start + 1
while i < len(text):
s = _strip_comment(text[i]).strip()
if not s:
i += 1
continue
if s.startswith("]"):
break
if BLOCK_RE.match(s) and BLOCK_RE.match(s).group(1) == "Component":
block = parse_block(text, i)
names = collect_page_names((block[0], block[1]))
if names:
comps.append(names)
i = block[2]
continue
i += 1
return comps
def dedup_crumbs(labels: list[str], icons: list[str]) -> tuple[list[str], list[str]]:
out_labels: list[str] = []
out_icons: list[str] = []
for lbl, ico in zip(labels, icons):
if out_labels and out_labels[-1] == lbl:
continue
out_labels.append(lbl)
out_icons.append(ico)
return out_labels, out_icons
def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
comps = parse_page_comps(settings)
registry = parse_page_registry(settings)
top_meta: dict[int, tuple[str, str]] = {}
for i, (icon, label) in enumerate(registry):
top_meta[i] = (icon, label)
nav_children: dict[str, dict[int, tuple[str, str, str]]] = {}
for names in comps:
for name in names:
pf = files.get(name)
if not pf:
continue
pending_icon = pending_label = None
section = ""
expect_section = False
for ln in read_lines(pf):
if SECTION_RE.match(ln):
expect_section = True
continue
ml = LABEL_RE.match(ln)
if ml:
if expect_section:
section = ml.group(1)
expect_section = False
else:
pending_label = ml.group(1)
continue
mi = ICON_RE.match(ln)
if mi:
pending_icon = mi.group(1)
mo = re.search(r"openSubPage\((\d+)\)", ln)
if mo:
pos = int(mo.group(1))
nav_children.setdefault(name, {})[pos] = (
pending_icon or "tune", pending_label or "", section)
pending_icon = pending_label = None
nav: dict[str, dict] = {}
for top_idx, names in enumerate(comps):
if not names:
continue
main = names[0]
main_icon, main_label = top_meta.get(top_idx, ("tune", main))
nav[main] = {"pageIdx": top_idx, "subPath": [],
"crumbIcons": [main_icon], "crumbLabels": [main_label]}
children = dict(nav_children.get(main, {}))
opened_via_subpage = set()
for owner, kids in nav_children.items():
owner_group = next((ns for ns in comps if owner in ns), None)
if not owner_group:
continue
for kpos in kids:
if kpos < len(owner_group):
opened_via_subpage.add(owner_group[kpos])
for pos in range(1, len(names)):
if pos not in children and names[pos] not in opened_via_subpage:
label = re.sub(r"(Detail)?Page$", "", names[pos])
label = re.sub(r"(?<!^)(?=[A-Z])", " ", label)
children[pos] = (main_icon, label, "")
for pos, (icon, label, section) in children.items():
if pos >= len(names):
continue
child = names[pos]
labels = [main_label] + ([section] if section else []) + [label]
icons = [main_icon] + ([icon] if section else []) + [icon]
labels, icons = dedup_crumbs(labels, icons)
nav[child] = {"pageIdx": top_idx, "subPath": [pos],
"crumbIcons": icons,
"crumbLabels": labels}
for gpos, (gicon, glabel, gsection) in nav_children.get(child, {}).items():
if gpos >= len(names):
continue
glabels = labels + ([gsection] if gsection else []) + [glabel]
gicons = icons + ([gicon] if gsection else []) + [gicon]
glabels, gicons = dedup_crumbs(glabels, gicons)
nav[names[gpos]] = {
"pageIdx": top_idx, "subPath": [pos, gpos],
"crumbIcons": gicons,
"crumbLabels": glabels}
return nav
def tokenize(text: str) -> list[str]:
toks: list[str] = []
for word in text.lower().split():
parts = [p for p in re.split(r"[^a-z0-9]+", word) if p]
for p in parts:
if p not in STOPWORDS and p not in toks:
toks.append(p)
if len(parts) > 1:
joined = "".join(parts)
if joined not in toks:
toks.append(joined)
return toks
SUBTEXT_RE = re.compile(r'^\s*(?:subtext|status):\s*qsTr\("([^"]+)"\)')
SECTION_RE = re.compile(r'^\s*SectionHeader\s*\{')
def extract_settings(files: dict[str, Path], nav: dict[str, dict]) -> list[dict]:
entries: list[dict] = []
for comp, meta in nav.items():
pf = files.get(comp)
if not pf:
continue
lines = read_lines(pf)
section = ""
i = 0
while i < len(lines):
if SECTION_RE.match(lines[i]):
for j in range(i + 1, min(i + 4, len(lines))):
m = LABEL_RE.match(lines[j])
if m:
section = m.group(1)
break
row_match = ROW_RE.match(lines[i])
if row_match:
row_type = row_match.group(1)
label = anchor = subtext = None
checked_path = toggled_path = None
for j in range(i + 1, min(i + 12, len(lines))):
if label is None:
m = LABEL_RE.match(lines[j])
if m:
label = m.group(1)
if anchor is None:
a = ANCHOR_RE.match(lines[j])
if a:
anchor = a.group(1)
if subtext is None:
st = SUBTEXT_RE.match(lines[j])
if st:
subtext = st.group(1)
if checked_path is None:
ch = CHECKED_RE.match(lines[j])
if ch:
checked_path = ch.group(1)
if toggled_path is None:
tg = ONTOGGLED_RE.match(lines[j])
if tg:
toggled_path = tg.group(1)
toggle_path = (
checked_path
if row_type == "ToggleRow" and checked_path and checked_path == toggled_path
else ""
)
if label and label not in SKIP_LABELS and anchor:
extra = " ".join(meta["crumbLabels"]) + \
" " + section + " " + (subtext or "")
entries.append({
"pageIdx": meta["pageIdx"], "subPath": meta["subPath"],
"crumbIcons": meta["crumbIcons"],
"crumbLabels": meta["crumbLabels"],
"title": label, "anchor": anchor,
"section": section,
"subtext": subtext or "",
"togglePath": toggle_path,
"keywords": " ".join(sorted(set(tokenize(label + " " + extra)))),
})
i += 1
return entries
def build_inverted_and_ranking(entries: list[dict]):
inverted: dict[str, list[int]] = defaultdict(list)
ranking: dict[str, dict[int, float]] = defaultdict(dict)
for idx, e in enumerate(entries):
fields = {"title": e["title"], "keywords": e["keywords"]}
seen: set[str] = set()
for field, text in fields.items():
weight = FIELD_WEIGHT.get(field, 0.2)
for tok in tokenize(text):
if idx not in inverted[tok]:
inverted[tok].append(idx)
ranking[tok][idx] = max(ranking[tok].get(idx, 0.0), weight)
seen.add(tok)
for tok, ids in inverted.items():
ids.sort(key=lambda i: ranking[tok][i], reverse=True)
return inverted, {t: {str(k): v for k, v in d.items()} for t, d in ranking.items()}
def main() -> int:
if len(sys.argv) != 3:
print(__doc__)
return 1
settings = Path(sys.argv[1])
out = Path(sys.argv[2])
files = discover_files(settings)
nav = build_nav_map(settings, files)
entries = extract_settings(files, nav)
inverted, ranking = build_inverted_and_ranking(entries)
for e in entries:
e.pop("keywords", None)
out.write_text(json.dumps({
"version": 2,
"entries": entries,
"inverted": inverted,
"ranking": ranking,
}, ensure_ascii=False, indent=2))
print(f"settings index: {len(entries)} entries, "
f"{len(inverted)} tokens -> {out}")
print("files:", len(files))
print("comps:", len(parse_page_comps(settings)))
print("registry:", len(parse_page_registry(settings)))
print("nav:", len(nav))
print("entries:", len(entries))
return 0
if __name__ == "__main__":
sys.exit(main())