Compare commits
12
Commits
v0.2.0
..
656bd0a54d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656bd0a54d | ||
|
|
355106cb2b | ||
|
|
865b5bda53 | ||
|
|
2b89c8e4a1 | ||
|
|
f01001dcfa | ||
|
|
61019204d8 | ||
|
|
bc6b0d50ab | ||
|
|
f27c9afc25 | ||
|
|
5962fc8354 | ||
|
|
1adcb9841c | ||
|
|
0e0f8555ed | ||
|
|
1d63e67554 |
@@ -15,3 +15,4 @@ dist/
|
||||
**/target/
|
||||
**/test-plugins/
|
||||
**/Charts/
|
||||
**/network-dev/
|
||||
|
||||
@@ -51,6 +51,20 @@ add_compile_options(
|
||||
-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)
|
||||
add_subdirectory(Plugins)
|
||||
|
||||
@@ -40,10 +40,6 @@ JsonObject {
|
||||
id: "tray",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "network",
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: "clock",
|
||||
enabled: true
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import qs.Config
|
||||
import qs.Helpers
|
||||
import qs.Modules.SysTray
|
||||
import qs.Modules.SysTray.Widgets
|
||||
import qs.Modules.Network
|
||||
import qs.Modules.Updates
|
||||
|
||||
RowLayout {
|
||||
@@ -185,15 +184,6 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: WrappedLoader {
|
||||
sourceComponent: NetworkWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "media"
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import qs.Config
|
||||
import qs.Helpers
|
||||
import qs.Modules.SysTray
|
||||
import qs.Modules.SysTray.Widgets
|
||||
import qs.Modules.Network
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -34,21 +34,11 @@ Item {
|
||||
implicitHeight: 50
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
MaterialIcon {
|
||||
id: searchIcon
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.large
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "search"
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
SearchBar {
|
||||
id: searchField
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: searchIcon.right
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
@@ -170,7 +160,6 @@ Item {
|
||||
id: lineText
|
||||
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
|
||||
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
|
||||
text: lineRow.modelData.text.trim()
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -336,6 +325,8 @@ Item {
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: ClipHistory.fuzzyQuery(searchField.text)
|
||||
|
||||
onValuesChanged: view.currentIndex = 0
|
||||
}
|
||||
|
||||
onCurrentItemChanged: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import QtQuick
|
||||
import qs.Config
|
||||
import qs.Components
|
||||
import qs.Modules.WSOverview
|
||||
import qs.Modules.Network
|
||||
import qs.Modules.SysTray.Popouts
|
||||
import qs.Modules.Updates
|
||||
|
||||
|
||||
@@ -52,11 +52,7 @@ Item {
|
||||
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
|
||||
topPadding: Appearance.padding.larger
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log(search.color);
|
||||
console.log(search.placeholderTextColor);
|
||||
forceActiveFocus();
|
||||
}
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
|
||||
Keys.onEscapePressed: root.visibilities.launcher = false
|
||||
Keys.onPressed: event => {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,64 @@ import qs.Components
|
||||
import qs.Config
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
property bool first
|
||||
property bool last
|
||||
property string settingAnchor
|
||||
|
||||
function flashHighlight(): void {
|
||||
flash.restart();
|
||||
}
|
||||
|
||||
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
topLeftRadius: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import qs.Config
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property bool animateScroll: false
|
||||
readonly property int cappedWidth: Math.min(800, width)
|
||||
default property Item contentChild
|
||||
readonly property alias flickable: flickable
|
||||
@@ -16,11 +17,100 @@ ColumnLayout {
|
||||
required property SettingsState sState
|
||||
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
|
||||
|
||||
MouseArea { // Prevent clicks from reaching flickable
|
||||
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
|
||||
Component.onCompleted: applySearchAnchor()
|
||||
|
||||
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
|
||||
implicitWidth: header.implicitWidth
|
||||
z: 1
|
||||
@@ -69,6 +159,14 @@ ColumnLayout {
|
||||
fadeAmount: 0.1
|
||||
topMargin: Appearance.padding.large
|
||||
|
||||
Behavior on contentY {
|
||||
enabled: root.animateScroll
|
||||
|
||||
Anim {
|
||||
type: Anim.DefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: flickable.focus = true
|
||||
}
|
||||
|
||||
@@ -9,8 +9,13 @@ CustomSwitch {
|
||||
readonly property alias bg: bg
|
||||
property alias first: bg.first
|
||||
property alias last: bg.last
|
||||
property string settingAnchor
|
||||
property string subtext
|
||||
|
||||
function flashHighlight(): void {
|
||||
bg.flashHighlight();
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
cLayer: 2
|
||||
horizontalPadding: Appearance.padding.largeIncreased
|
||||
|
||||
@@ -34,6 +34,12 @@ ColumnLayout {
|
||||
target: root.sState
|
||||
value: searchField.text.length > 0
|
||||
}
|
||||
|
||||
Binding {
|
||||
property: "searchText"
|
||||
target: root.sState
|
||||
value: searchField.text
|
||||
}
|
||||
}
|
||||
|
||||
NavLocations {
|
||||
|
||||
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Modules.Settings
|
||||
@@ -9,7 +10,33 @@ import qs.Modules.Settings
|
||||
VerticalFadeFlickable {
|
||||
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
|
||||
readonly property string search: sState.searchText
|
||||
readonly property bool searching: search.length > 0
|
||||
|
||||
bottomMargin: Appearance.padding.large
|
||||
contentHeight: content.implicitHeight
|
||||
@@ -30,14 +57,14 @@ VerticalFadeFlickable {
|
||||
Repeater {
|
||||
id: list
|
||||
|
||||
model: PageRegistry.pages
|
||||
model: root.searching ? [] : PageRegistry.pages
|
||||
|
||||
CustomRect {
|
||||
id: item
|
||||
|
||||
required property int index
|
||||
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 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 isCurrentPage: index === root.sState.currentPageIdx
|
||||
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 {
|
||||
|
||||
@@ -30,6 +30,7 @@ PageBase {
|
||||
DefaultRow {
|
||||
first: true
|
||||
icon: "terminal"
|
||||
settingAnchor: "apps-default-terminal"
|
||||
status: Config.general.apps.terminal.join(" ")
|
||||
text: qsTr("Terminal")
|
||||
|
||||
@@ -38,6 +39,7 @@ PageBase {
|
||||
|
||||
DefaultRow {
|
||||
icon: "volume_up"
|
||||
settingAnchor: "apps-default-audio"
|
||||
status: Config.general.apps.audio.join(" ")
|
||||
text: qsTr("Audio")
|
||||
|
||||
@@ -46,6 +48,7 @@ PageBase {
|
||||
|
||||
DefaultRow {
|
||||
icon: "play_circle"
|
||||
settingAnchor: "apps-default-playback"
|
||||
status: Config.general.apps.playback.join(" ")
|
||||
text: qsTr("Media playback")
|
||||
|
||||
@@ -55,6 +58,7 @@ PageBase {
|
||||
DefaultRow {
|
||||
icon: "folder"
|
||||
last: true
|
||||
settingAnchor: "apps-default-file-manager"
|
||||
status: Config.general.apps.explorer.join(" ")
|
||||
text: qsTr("File manager")
|
||||
|
||||
@@ -70,6 +74,7 @@ PageBase {
|
||||
first: true
|
||||
icon: "apps"
|
||||
last: true
|
||||
settingAnchor: "apps-all-apps"
|
||||
status: qsTr("Browse installed apps, set favorites and hidden")
|
||||
text: qsTr("All apps")
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ PageBase {
|
||||
first: true
|
||||
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
|
||||
label: qsTr("Output")
|
||||
settingAnchor: "audio-output"
|
||||
value: Audio.volume
|
||||
valueLabel: Math.round(value * 100) + "%"
|
||||
|
||||
@@ -55,6 +56,7 @@ PageBase {
|
||||
first: true
|
||||
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
|
||||
label: qsTr("Input")
|
||||
settingAnchor: "audio-input"
|
||||
value: Audio.sourceVolume
|
||||
valueLabel: Math.round(value * 100) + "%"
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ PageBase {
|
||||
first: true
|
||||
last: true
|
||||
menuItems: root.clockFormats
|
||||
settingAnchor: "bar-clock-format"
|
||||
subtext: qsTr("Change how time is displayed in the widget")
|
||||
text: qsTr("Time format")
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.bar.tray.showAudio
|
||||
first: true
|
||||
settingAnchor: "bar-status-speakers"
|
||||
text: qsTr("Speakers")
|
||||
|
||||
onToggled: Config.bar.tray.showAudio = checked
|
||||
@@ -32,6 +33,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.bar.tray.showMicrophone
|
||||
settingAnchor: "bar-status-mic"
|
||||
text: qsTr("Microphone")
|
||||
|
||||
onToggled: Config.bar.tray.showMicrophone = checked
|
||||
@@ -62,6 +64,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.bar.tray.showPower
|
||||
last: true
|
||||
settingAnchor: "bar-status-power"
|
||||
text: qsTr("Battery")
|
||||
|
||||
onToggled: Config.bar.tray.showPower = checked
|
||||
@@ -75,6 +78,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.bar.popouts.audio
|
||||
first: true
|
||||
settingAnchor: "bar-status-audio-popout"
|
||||
subtext: qsTr("Show a details popout when hovering the audio icons")
|
||||
text: qsTr("Audio popout on hover")
|
||||
|
||||
@@ -84,6 +88,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.bar.popouts.upower
|
||||
last: true
|
||||
settingAnchor: "bar-status-power-popout"
|
||||
subtext: qsTr("Show a details popout when hovering the power icon")
|
||||
text: qsTr("Power popout on hover")
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ PageBase {
|
||||
first: true
|
||||
from: 12
|
||||
last: true
|
||||
settingAnchor: "bar-tray-iconsize"
|
||||
stepSize: 1
|
||||
text: qsTr("Icon size")
|
||||
to: 24
|
||||
|
||||
@@ -26,6 +26,7 @@ PageBase {
|
||||
checked: Config.bar.autoHide
|
||||
first: true
|
||||
last: true
|
||||
settingAnchor: "bar-autohide"
|
||||
subtext: qsTr("Hide the bar, reveal on hover")
|
||||
text: qsTr("Auto hide")
|
||||
|
||||
@@ -40,6 +41,7 @@ PageBase {
|
||||
NavRow {
|
||||
first: true
|
||||
icon: "widgets"
|
||||
settingAnchor: "bar-tray"
|
||||
status: qsTr("System tray icons")
|
||||
text: qsTr("Tray")
|
||||
|
||||
@@ -48,6 +50,7 @@ PageBase {
|
||||
|
||||
NavRow {
|
||||
icon: "signal_cellular_alt"
|
||||
settingAnchor: "bar-statusicons"
|
||||
status: qsTr("Visible indicators")
|
||||
text: qsTr("Status icons")
|
||||
|
||||
@@ -57,6 +60,7 @@ PageBase {
|
||||
NavRow {
|
||||
icon: "schedule"
|
||||
last: true
|
||||
settingAnchor: "bar-clock"
|
||||
status: qsTr("Date, icon, background")
|
||||
text: qsTr("Clock")
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ PageBase {
|
||||
checked: Config.dashboard.enabled
|
||||
first: true
|
||||
last: true
|
||||
settingAnchor: "dashboard-enabled"
|
||||
text: qsTr("Enabled")
|
||||
|
||||
onToggled: Config.dashboard.enabled = checked
|
||||
|
||||
@@ -28,6 +28,7 @@ PageBase {
|
||||
checked: Config.launcher.enabled
|
||||
first: true
|
||||
last: true
|
||||
settingAnchor: "launcher-enabled"
|
||||
text: qsTr("Enabled")
|
||||
|
||||
onToggled: Config.launcher.enabled = checked
|
||||
@@ -41,6 +42,7 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 1
|
||||
settingAnchor: "launcher-max-items-shown"
|
||||
stepSize: 1
|
||||
text: qsTr("Max items shown")
|
||||
to: 20
|
||||
@@ -51,6 +53,7 @@ PageBase {
|
||||
|
||||
SpinRow {
|
||||
from: 1
|
||||
settingAnchor: "launcher-max-wallpapers"
|
||||
stepSize: 1
|
||||
text: qsTr("Max wallpapers")
|
||||
to: 30
|
||||
@@ -62,6 +65,7 @@ PageBase {
|
||||
SpinRow {
|
||||
from: 0
|
||||
last: true
|
||||
settingAnchor: "launcher-drag-threshold"
|
||||
stepSize: 5
|
||||
subtext: qsTr("Pixels dragged before the launcher opens")
|
||||
text: qsTr("Drag threshold")
|
||||
@@ -80,6 +84,7 @@ PageBase {
|
||||
checked: Config.launcher.enableDangerousActions
|
||||
first: true
|
||||
last: true
|
||||
settingAnchor: "launcher-enable-dangerous-actions"
|
||||
subtext: qsTr("Allow actions that shut down or log out")
|
||||
text: qsTr("Enable dangerous actions")
|
||||
|
||||
@@ -94,6 +99,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.launcher.useFuzzy.apps
|
||||
first: true
|
||||
settingAnchor: "launcher-apps"
|
||||
text: qsTr("Apps")
|
||||
|
||||
onToggled: Config.launcher.useFuzzy.apps = checked
|
||||
@@ -101,20 +107,15 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.launcher.useFuzzy.actions
|
||||
settingAnchor: "launcher-actions"
|
||||
text: qsTr("Actions")
|
||||
|
||||
onToggled: Config.launcher.useFuzzy.actions = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.launcher.useFuzzy.schemes
|
||||
text: qsTr("Schemes")
|
||||
|
||||
onToggled: Config.launcher.useFuzzy.schemes = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.launcher.useFuzzy.variants
|
||||
settingAnchor: "launcher-variants"
|
||||
text: qsTr("Variants")
|
||||
|
||||
onToggled: Config.launcher.useFuzzy.variants = checked
|
||||
@@ -123,6 +124,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.launcher.useFuzzy.wallpapers
|
||||
last: true
|
||||
settingAnchor: "launcher-wallpapers"
|
||||
text: qsTr("Wallpapers")
|
||||
|
||||
onToggled: Config.launcher.useFuzzy.wallpapers = checked
|
||||
|
||||
@@ -28,6 +28,7 @@ PageBase {
|
||||
checked: Config.dashboard.performance.enabled
|
||||
first: true
|
||||
last: true
|
||||
settingAnchor: "resources-enabled"
|
||||
text: qsTr("Enabled")
|
||||
|
||||
onToggled: Config.dashboard.performance.enabled = checked
|
||||
@@ -41,6 +42,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showBattery
|
||||
first: true
|
||||
settingAnchor: "resources-battery"
|
||||
text: qsTr("Battery")
|
||||
|
||||
onToggled: Config.dashboard.performance.showBattery = checked
|
||||
@@ -48,6 +50,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showGpu
|
||||
settingAnchor: "resources-gpu"
|
||||
text: qsTr("GPU")
|
||||
|
||||
onToggled: Config.dashboard.performance.showGpu = checked
|
||||
@@ -55,6 +58,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showCpu
|
||||
settingAnchor: "resources-cpu"
|
||||
text: qsTr("CPU")
|
||||
|
||||
onToggled: Config.dashboard.performance.showCpu = checked
|
||||
@@ -62,6 +66,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showMemory
|
||||
settingAnchor: "resources-memory"
|
||||
text: qsTr("Memory")
|
||||
|
||||
onToggled: Config.dashboard.performance.showMemory = checked
|
||||
@@ -69,6 +74,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showStorage
|
||||
settingAnchor: "resources-storage"
|
||||
text: qsTr("Storage")
|
||||
|
||||
onToggled: Config.dashboard.performance.showStorage = checked
|
||||
@@ -77,6 +83,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.performance.showNetwork
|
||||
last: true
|
||||
settingAnchor: "resources-network"
|
||||
text: qsTr("Network")
|
||||
|
||||
onToggled: Config.dashboard.performance.showNetwork = checked
|
||||
|
||||
@@ -26,6 +26,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.sidebar.enabled
|
||||
first: true
|
||||
settingAnchor: "sidebar-enabled"
|
||||
text: qsTr("Enabled")
|
||||
|
||||
onToggled: Config.sidebar.enabled = checked
|
||||
@@ -34,6 +35,7 @@ PageBase {
|
||||
SpinRow {
|
||||
from: 0
|
||||
last: true
|
||||
settingAnchor: "sidebar-drag-threshold"
|
||||
stepSize: 5
|
||||
subtext: qsTr("Pixels dragged before the sidebar opens")
|
||||
text: qsTr("Drag threshold")
|
||||
|
||||
@@ -16,6 +16,7 @@ PageBase {
|
||||
NavRow {
|
||||
first: true
|
||||
icon: "dock_to_bottom"
|
||||
settingAnchor: "panels-bar"
|
||||
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
|
||||
text: qsTr("Bar")
|
||||
|
||||
@@ -24,6 +25,7 @@ PageBase {
|
||||
|
||||
NavRow {
|
||||
icon: "dashboard"
|
||||
settingAnchor: "panels-dashboard"
|
||||
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
text: qsTr("Dashboard")
|
||||
|
||||
@@ -32,6 +34,7 @@ PageBase {
|
||||
|
||||
NavRow {
|
||||
icon: "insert_chart"
|
||||
settingAnchor: "panels-resources"
|
||||
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
text: qsTr("Resources")
|
||||
|
||||
@@ -40,6 +43,7 @@ PageBase {
|
||||
|
||||
NavRow {
|
||||
icon: "apps"
|
||||
settingAnchor: "panels-launcher"
|
||||
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
text: qsTr("Launcher")
|
||||
|
||||
@@ -49,6 +53,7 @@ PageBase {
|
||||
NavRow {
|
||||
icon: "dock_to_right"
|
||||
last: true
|
||||
settingAnchor: "panels-sidebar"
|
||||
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
text: qsTr("Sidebar")
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.screenshot.enable_pp
|
||||
first: true
|
||||
settingAnchor: "screenshot-enable"
|
||||
text: qsTr("Enable effects")
|
||||
|
||||
onToggled: Config.screenshot.enable_pp = checked
|
||||
@@ -37,6 +38,7 @@ PageBase {
|
||||
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
|
||||
enabled: Config.screenshot.enable_pp
|
||||
last: true
|
||||
settingAnchor: "screenshot-mode"
|
||||
subtext: qsTr("Automatic or manual effect values")
|
||||
text: qsTr("Effects mode")
|
||||
|
||||
@@ -67,6 +69,7 @@ PageBase {
|
||||
checked: Config.screenshot.rounding
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
|
||||
first: true
|
||||
settingAnchor: "screenshot-enable-rounded-corners"
|
||||
text: qsTr("Enable rounded corners")
|
||||
|
||||
onToggled: Config.screenshot.rounding = checked
|
||||
@@ -76,6 +79,7 @@ PageBase {
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
|
||||
from: 0
|
||||
last: true
|
||||
settingAnchor: "screenshot-corner-radius"
|
||||
stepSize: 1
|
||||
text: qsTr("Corner radius")
|
||||
to: 50
|
||||
@@ -95,6 +99,7 @@ PageBase {
|
||||
checked: Config.screenshot.shadow
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
|
||||
first: true
|
||||
settingAnchor: "screenshot-enable-shadow"
|
||||
text: qsTr("Enable shadow")
|
||||
|
||||
onToggled: Config.screenshot.shadow = checked
|
||||
@@ -103,6 +108,7 @@ PageBase {
|
||||
SpinRow {
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
|
||||
from: 0
|
||||
settingAnchor: "screenshot-shadow-blur-amount"
|
||||
stepSize: 1
|
||||
text: qsTr("Shadow blur amount")
|
||||
to: 100
|
||||
@@ -117,6 +123,7 @@ PageBase {
|
||||
SpinRow {
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
|
||||
from: -100
|
||||
settingAnchor: "screenshot-shadow-horizontal-offset"
|
||||
stepSize: 10
|
||||
text: qsTr("Shadow horizontal offset")
|
||||
to: 100
|
||||
@@ -132,6 +139,7 @@ PageBase {
|
||||
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
|
||||
from: -100
|
||||
last: true
|
||||
settingAnchor: "screenshot-shadow-vertical-offset"
|
||||
stepSize: 10
|
||||
text: qsTr("Shadow vertical offset")
|
||||
to: 100
|
||||
|
||||
@@ -25,6 +25,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.notifs.showInFullscreen
|
||||
first: true
|
||||
settingAnchor: "notif-show-in-fullscreen"
|
||||
subtext: qsTr("Whether notifications appear over fullscreen apps")
|
||||
text: qsTr("Show in fullscreen")
|
||||
|
||||
@@ -33,6 +34,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.notifs.expire
|
||||
settingAnchor: "notif-expire-automatically"
|
||||
subtext: qsTr("Dismiss notifications after their timeout")
|
||||
text: qsTr("Expire automatically")
|
||||
|
||||
@@ -41,6 +43,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.notifs.openExpanded
|
||||
settingAnchor: "notif-open-expanded"
|
||||
subtext: qsTr("Show notifications expanded by default")
|
||||
text: qsTr("Open expanded")
|
||||
|
||||
@@ -49,6 +52,7 @@ PageBase {
|
||||
|
||||
SpinRow {
|
||||
from: 1000
|
||||
settingAnchor: "notif-default-timeout"
|
||||
stepSize: 500
|
||||
subtext: qsTr("Time before a notification dismisses (ms)")
|
||||
text: qsTr("Default timeout")
|
||||
@@ -61,6 +65,7 @@ PageBase {
|
||||
SpinRow {
|
||||
from: 1
|
||||
last: true
|
||||
settingAnchor: "notif-group-preview-count"
|
||||
stepSize: 1
|
||||
subtext: qsTr("Notifications shown per group before collapsing")
|
||||
text: qsTr("Group preview count")
|
||||
@@ -78,6 +83,7 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 1
|
||||
settingAnchor: "notif-visible-toasts"
|
||||
stepSize: 1
|
||||
subtext: qsTr("Maximum number of toasts shown at once")
|
||||
text: qsTr("Visible toasts")
|
||||
@@ -89,6 +95,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.utilities.toasts.chargingChanged
|
||||
settingAnchor: "notif-charging-changes"
|
||||
text: qsTr("Charging changes")
|
||||
|
||||
onToggled: Config.utilities.toasts.chargingChanged = checked
|
||||
@@ -96,6 +103,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.utilities.toasts.gameModeChanged
|
||||
settingAnchor: "notif-game-mode-changes"
|
||||
text: qsTr("Game mode changes")
|
||||
|
||||
onToggled: Config.utilities.toasts.gameModeChanged = checked
|
||||
@@ -110,6 +118,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.utilities.toasts.audioOutputChanged
|
||||
settingAnchor: "notif-audio-output-changes"
|
||||
text: qsTr("Audio output changes")
|
||||
|
||||
onToggled: Config.utilities.toasts.audioOutputChanged = checked
|
||||
@@ -118,6 +127,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.utilities.toasts.audioInputChanged
|
||||
last: true
|
||||
settingAnchor: "notif-audio-input-changes"
|
||||
text: qsTr("Audio input changes")
|
||||
|
||||
onToggled: Config.utilities.toasts.audioInputChanged = checked
|
||||
|
||||
@@ -71,6 +71,7 @@ PageBase {
|
||||
first: true
|
||||
icon: "notifications"
|
||||
last: true
|
||||
settingAnchor: "services-notifications"
|
||||
status: qsTr("Notifications, toasts, timeouts")
|
||||
text: qsTr("Notifications")
|
||||
|
||||
@@ -85,6 +86,7 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 100
|
||||
settingAnchor: "services-media-refresh"
|
||||
stepSize: 50
|
||||
subtext: qsTr("How often the media position updates (ms)")
|
||||
text: qsTr("Media refresh")
|
||||
@@ -97,6 +99,7 @@ PageBase {
|
||||
SpinRow {
|
||||
from: 0.5
|
||||
last: true
|
||||
settingAnchor: "services-system-stats-refresh"
|
||||
stepSize: 0.5
|
||||
subtext: qsTr("CPU, memory and GPU update interval (seconds)")
|
||||
text: qsTr("System stats refresh")
|
||||
@@ -118,6 +121,7 @@ PageBase {
|
||||
first: true
|
||||
last: true
|
||||
menuItems: playerVariants.instances
|
||||
settingAnchor: "services-default-player"
|
||||
subtext: qsTr("Preferred media player when several are open")
|
||||
text: qsTr("Default player")
|
||||
|
||||
@@ -132,6 +136,7 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 1
|
||||
settingAnchor: "services-volume-step"
|
||||
stepSize: 1
|
||||
subtext: qsTr("Amount the volume changes per input (%)")
|
||||
text: qsTr("Volume step")
|
||||
@@ -143,6 +148,7 @@ PageBase {
|
||||
|
||||
SpinRow {
|
||||
from: 1
|
||||
settingAnchor: "services-brightness-step"
|
||||
stepSize: 1
|
||||
subtext: qsTr("Amount the brightness changes per input (%)")
|
||||
text: qsTr("Brightness step")
|
||||
@@ -154,6 +160,7 @@ PageBase {
|
||||
|
||||
SpinRow {
|
||||
from: 1
|
||||
settingAnchor: "services-brightness-minimum"
|
||||
stepSize: 1
|
||||
subtext: qsTr("Lowest allowed brightness (%)")
|
||||
text: qsTr("Brightness minimum")
|
||||
@@ -166,6 +173,7 @@ PageBase {
|
||||
SpinRow {
|
||||
from: 50
|
||||
last: true
|
||||
settingAnchor: "services-max-volume"
|
||||
stepSize: 5
|
||||
subtext: qsTr("Upper limit for output volume (%)")
|
||||
text: qsTr("Max volume")
|
||||
@@ -183,6 +191,7 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 10
|
||||
settingAnchor: "services-visualizer-resolution"
|
||||
stepSize: 2
|
||||
subtext: qsTr("Resolution of the audio visualizer")
|
||||
text: qsTr("Visualizer resolution")
|
||||
@@ -194,6 +203,7 @@ PageBase {
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.general.color.smart
|
||||
settingAnchor: "services-smart-color-scheme"
|
||||
subtext: qsTr("Derive theme mode from the wallpaper")
|
||||
text: qsTr("Smart color scheme")
|
||||
|
||||
@@ -205,6 +215,7 @@ PageBase {
|
||||
last: true
|
||||
menuItems: root.gpuItems
|
||||
menuOnTop: true
|
||||
settingAnchor: "services-gpu"
|
||||
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
|
||||
text: qsTr("GPU")
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
checked: Config.background.enabled
|
||||
first: true
|
||||
settingAnchor: "style-display-wallpaper"
|
||||
text: qsTr("Display wallpaper")
|
||||
|
||||
onToggled: Config.background.enabled = checked
|
||||
@@ -57,6 +58,7 @@ PageBase {
|
||||
ToggleRow {
|
||||
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: DynamicColors.transparency.enabled
|
||||
settingAnchor: "style-transparency"
|
||||
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
|
||||
text: qsTr("Transparency")
|
||||
|
||||
@@ -67,6 +69,7 @@ PageBase {
|
||||
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: !DynamicColors.light
|
||||
last: true
|
||||
settingAnchor: "style-dark-theme"
|
||||
text: qsTr("Dark theme")
|
||||
|
||||
onToggled: DynamicColors.setMode(checked ? "dark" : "light")
|
||||
@@ -88,6 +91,7 @@ PageBase {
|
||||
|
||||
checked: Config.general.color.scheduleDark
|
||||
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)
|
||||
text: qsTr("Schedule dark mode")
|
||||
|
||||
@@ -129,6 +133,7 @@ PageBase {
|
||||
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: Config.general.color.scheduleHyprsunset
|
||||
last: true
|
||||
settingAnchor: "style-schedule-hyprsunset"
|
||||
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
|
||||
text: qsTr("Schedule hyprsunset")
|
||||
|
||||
|
||||
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,19 @@ QtObject {
|
||||
property bool animatingContainer
|
||||
property int currentPageIdx
|
||||
property bool isWindow
|
||||
property string lastAnchor
|
||||
property list<int> pendingSubPath
|
||||
property ShellScreen screen
|
||||
property string searchAnchor
|
||||
property bool searchOpen
|
||||
property string searchText
|
||||
property DesktopEntry selectedApp
|
||||
property BluetoothDevice selectedBtDevice
|
||||
property string selectedWallpaperCategory
|
||||
property list<int> subPageIdxStack
|
||||
|
||||
signal close
|
||||
signal highlightSetting(anchor: string)
|
||||
signal subPageClosed
|
||||
signal subPageOpened(idx: int)
|
||||
|
||||
@@ -24,10 +29,38 @@ QtObject {
|
||||
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 {
|
||||
subPageIdxStack.push(idx);
|
||||
subPageOpened(idx);
|
||||
}
|
||||
|
||||
onCurrentPageIdxChanged: subPageIdxStack.length = 0
|
||||
onCurrentPageIdxChanged: {
|
||||
subPageIdxStack = pendingSubPath;
|
||||
pendingSubPath = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,11 @@ RowLayout {
|
||||
id: "audio",
|
||||
item: child
|
||||
};
|
||||
if (child.objectName === "networkWidget" && Config.bar.popouts.network)
|
||||
return {
|
||||
id: "network",
|
||||
item: child
|
||||
};
|
||||
if (child.objectName === "upowerWidget" && Config.bar.popouts.upower)
|
||||
return {
|
||||
id: "upower",
|
||||
@@ -146,6 +151,10 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NetworkWidget {
|
||||
objectName: "networkWidget"
|
||||
}
|
||||
|
||||
UPowerWidget {
|
||||
Layout.fillHeight: true
|
||||
objectName: "upowerWidget"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,7 @@ void BlobGroup::setColor(const QColor& c) {
|
||||
}
|
||||
|
||||
void BlobGroup::setCornerFill(bool e) {
|
||||
if (m_cornerFill == e)
|
||||
return;
|
||||
if (m_cornerFill == e) return;
|
||||
m_cornerFill = e;
|
||||
emit cornerFillChanged();
|
||||
markDirty();
|
||||
|
||||
@@ -9,11 +9,15 @@ class BlobShape;
|
||||
class BlobInvertedRect;
|
||||
|
||||
class BlobGroup : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged)
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(
|
||||
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||
smoothingChanged)
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
Q_PROPERTY(
|
||||
bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY
|
||||
cornerFillChanged)
|
||||
|
||||
public:
|
||||
explicit BlobGroup(QObject* parent = nullptr);
|
||||
@@ -27,14 +31,12 @@ Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFill
|
||||
|
||||
void setColor(const QColor& c);
|
||||
|
||||
[[nodiscard]] bool cornerFill() const {
|
||||
return m_cornerFill;
|
||||
}
|
||||
[[nodiscard]] bool cornerFill() const { return m_cornerFill; }
|
||||
|
||||
void setCornerFill(bool e);
|
||||
void setCornerFill(bool e);
|
||||
|
||||
void addShape(BlobShape* shape);
|
||||
void removeShape(BlobShape* shape);
|
||||
void addShape(BlobShape* shape);
|
||||
void removeShape(BlobShape* shape);
|
||||
|
||||
void setInvertedRect(BlobInvertedRect* rect);
|
||||
void clearInvertedRect(BlobInvertedRect* rect);
|
||||
@@ -49,16 +51,16 @@ void removeShape(BlobShape* shape);
|
||||
void markShapeDirty(BlobShape* source);
|
||||
void ensurePhysicsUpdated();
|
||||
|
||||
signals:
|
||||
void smoothingChanged();
|
||||
void colorChanged();
|
||||
void cornerFillChanged();
|
||||
signals:
|
||||
void smoothingChanged();
|
||||
void colorChanged();
|
||||
void cornerFillChanged();
|
||||
|
||||
private:
|
||||
qreal m_smoothing = 32.0;
|
||||
QColor m_color{ 0x44, 0x88, 0xff };
|
||||
bool m_cornerFill = true;
|
||||
QList<BlobShape*> m_shapes;
|
||||
BlobInvertedRect* m_invertedRect = nullptr;
|
||||
bool m_physicsUpdated = false;
|
||||
private:
|
||||
qreal m_smoothing = 32.0;
|
||||
QColor m_color{0x44, 0x88, 0xff};
|
||||
bool m_cornerFill = true;
|
||||
QList<BlobShape*> m_shapes;
|
||||
BlobInvertedRect* m_invertedRect = nullptr;
|
||||
bool m_physicsUpdated = false;
|
||||
};
|
||||
|
||||
@@ -32,11 +32,9 @@ void BlobRect::updatePolish() {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this]() {
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
if (m_group) m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
@@ -184,8 +182,7 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
|
||||
|
||||
bool BlobRect::isCornerExcluded(const BlobShape* other) const {
|
||||
for (const auto& ptr : m_excludeCorners) {
|
||||
if (ptr == other)
|
||||
return true;
|
||||
if (ptr == other) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -203,8 +200,15 @@ QQmlListProperty<BlobRect> BlobRect::exclude() {
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
|
||||
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt,
|
||||
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast);
|
||||
return QQmlListProperty<BlobRect>(
|
||||
this,
|
||||
nullptr,
|
||||
&excludeCornersAppend,
|
||||
&excludeCornersCount,
|
||||
&excludeCornersAt,
|
||||
&excludeCornersClear,
|
||||
&excludeCornersReplace,
|
||||
&excludeCornersRemoveLast);
|
||||
}
|
||||
|
||||
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
||||
@@ -249,11 +253,11 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||
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);
|
||||
self->m_excludeCorners.append(rect);
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
if (self->m_group) self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
@@ -262,36 +266,33 @@ qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
|
||||
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);
|
||||
return self->m_excludeCorners.at(index);
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_excludeCorners.isEmpty())
|
||||
return;
|
||||
if (self->m_excludeCorners.isEmpty()) return;
|
||||
self->m_excludeCorners.clear();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
if (self->m_group) self->m_group->markDirty();
|
||||
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);
|
||||
self->m_excludeCorners[index] = rect;
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
if (self->m_group) self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_excludeCorners.isEmpty())
|
||||
return;
|
||||
if (self->m_excludeCorners.isEmpty()) return;
|
||||
self->m_excludeCorners.removeLast();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
if (self->m_group) self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
@@ -319,11 +320,9 @@ void BlobRect::checkAtRest(float speed) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this]() {
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
if (m_group) m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,32 @@
|
||||
#include <qqmllist.h>
|
||||
|
||||
class BlobRect : public BlobShape {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged)
|
||||
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
|
||||
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
|
||||
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)
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(
|
||||
qreal stiffness READ stiffness WRITE setStiffness NOTIFY
|
||||
stiffnessChanged)
|
||||
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||
Q_PROPERTY(
|
||||
qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
|
||||
deformScaleChanged)
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
|
||||
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:
|
||||
explicit BlobRect(QQuickItem* parent = nullptr);
|
||||
@@ -52,12 +66,12 @@ Q_PROPERTY(
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> exclude();
|
||||
QQmlListProperty<BlobRect> excludeCorners();
|
||||
QQmlListProperty<BlobRect> exclude();
|
||||
QQmlListProperty<BlobRect> excludeCorners();
|
||||
|
||||
bool isExcluded(const BlobShape* other) const override;
|
||||
bool isCornerExcluded(const BlobShape* other) const override;
|
||||
void cornerRadii(float out[4]) const override;
|
||||
bool isExcluded(const BlobShape* other) const override;
|
||||
bool isCornerExcluded(const BlobShape* other) const override;
|
||||
void cornerRadii(float out[4]) const override;
|
||||
|
||||
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||
|
||||
@@ -77,16 +91,16 @@ void cornerRadii(float out[4]) const override;
|
||||
|
||||
void setBottomRightRadius(qreal r);
|
||||
|
||||
signals:
|
||||
void stiffnessChanged();
|
||||
void dampingChanged();
|
||||
void deformScaleChanged();
|
||||
void excludeChanged();
|
||||
void excludeCornersChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
signals:
|
||||
void stiffnessChanged();
|
||||
void dampingChanged();
|
||||
void deformScaleChanged();
|
||||
void excludeChanged();
|
||||
void excludeCornersChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
|
||||
protected:
|
||||
void updatePolish() override;
|
||||
@@ -121,20 +135,25 @@ void bottomRightRadiusChanged();
|
||||
qreal m_bottomLeftRadius = -1;
|
||||
qreal m_bottomRightRadius = -1;
|
||||
|
||||
QList<QPointer<BlobRect> > m_exclude;
|
||||
QList<QPointer<BlobRect> > m_excludeCorners;
|
||||
QList<QPointer<BlobRect>> m_exclude;
|
||||
QList<QPointer<BlobRect>> m_excludeCorners;
|
||||
|
||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeAt(
|
||||
QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeClear(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 qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeCornersAppend(
|
||||
QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeCornersAt(
|
||||
QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeCornersReplace(
|
||||
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
};
|
||||
|
||||
@@ -38,8 +38,7 @@ static float cornerFillFactor(float sd, float smoothFactor) {
|
||||
return std::max(outside, inside);
|
||||
}
|
||||
|
||||
BlobShape::BlobShape(QQuickItem* parent)
|
||||
: QQuickItem(parent) {
|
||||
BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setFlag(ItemHasContents);
|
||||
}
|
||||
|
||||
@@ -312,13 +311,10 @@ void BlobShape::updatePolish() {
|
||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
||||
|
||||
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
|
||||
if (j == i)
|
||||
continue;
|
||||
if (riExcludeMask & (1 << j))
|
||||
continue;
|
||||
if (j == i) continue;
|
||||
if (riExcludeMask & (1 << j)) continue;
|
||||
BlobShape* const sj = rectShapes[j];
|
||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
|
||||
continue;
|
||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) continue;
|
||||
const auto& rj = m_cachedRects[j];
|
||||
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);
|
||||
|
||||
@@ -55,11 +55,11 @@ class BlobShape : public QQuickItem {
|
||||
|
||||
virtual bool isExcluded(const BlobShape*) const { return false; }
|
||||
|
||||
virtual bool isCornerExcluded(const BlobShape* /*other*/) const {
|
||||
return false;
|
||||
}
|
||||
virtual bool isCornerExcluded(const BlobShape* /*other*/) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void cornerRadii(float out[4]) const;
|
||||
virtual void cornerRadii(float out[4]) const;
|
||||
|
||||
virtual void updatePhysics() {}
|
||||
|
||||
@@ -67,25 +67,25 @@ virtual void cornerRadii(float out[4]) const;
|
||||
virtual void unregisterFromGroup();
|
||||
void updateCenteredDeformMatrix();
|
||||
|
||||
BlobGroup* m_group = nullptr;
|
||||
qreal m_radius = 0;
|
||||
QMatrix4x4 m_deformMatrix; // identity by default
|
||||
QMatrix4x4 m_centeredDeformMatrix;
|
||||
BlobGroup* m_group = nullptr;
|
||||
qreal m_radius = 0;
|
||||
QMatrix4x4 m_deformMatrix; // identity by default
|
||||
QMatrix4x4 m_centeredDeformMatrix;
|
||||
|
||||
// Cached data from updatePolish
|
||||
float m_cachedPaddedX = 0;
|
||||
float m_cachedPaddedY = 0;
|
||||
float m_cachedPaddedW = 0;
|
||||
float m_cachedPaddedH = 0;
|
||||
float m_pendingDw = 0;
|
||||
float m_pendingDh = 0;
|
||||
QRectF m_localPaddedRect;
|
||||
QVector<BlobRectData> m_cachedRects;
|
||||
int m_cachedMyIndex = -2;
|
||||
float m_pendingDx = 0;
|
||||
float m_pendingDy = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_cachedInvertedRadius = 0;
|
||||
float m_cachedInvertedOuter[4] = {};
|
||||
float m_cachedInvertedInner[4] = {};
|
||||
// Cached data from updatePolish
|
||||
float m_cachedPaddedX = 0;
|
||||
float m_cachedPaddedY = 0;
|
||||
float m_cachedPaddedW = 0;
|
||||
float m_cachedPaddedH = 0;
|
||||
float m_pendingDw = 0;
|
||||
float m_pendingDh = 0;
|
||||
QRectF m_localPaddedRect;
|
||||
QVector<BlobRectData> m_cachedRects;
|
||||
int m_cachedMyIndex = -2;
|
||||
float m_pendingDx = 0;
|
||||
float m_pendingDy = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_cachedInvertedRadius = 0;
|
||||
float m_cachedInvertedOuter[4] = {};
|
||||
float m_cachedInvertedInner[4] = {};
|
||||
};
|
||||
|
||||
@@ -24,11 +24,12 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
|
||||
qt_standard_project_setup(REQUIRES 6.9)
|
||||
|
||||
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}
|
||||
URI ${arg_URI}
|
||||
VERSION 1.0
|
||||
SOURCES ${arg_SOURCES}
|
||||
RESOURCES ${arg_RESOURCES}
|
||||
)
|
||||
|
||||
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})
|
||||
endfunction()
|
||||
|
||||
set_source_files_properties("${SETTINGS_INDEX_JSON}" PROPERTIES QT_RESOURCE_ALIAS "settings-index.json")
|
||||
|
||||
qml_module(ZShell
|
||||
URI ZShell
|
||||
SOURCES
|
||||
@@ -57,6 +60,8 @@ qml_module(ZShell
|
||||
toaster.hpp toaster.cpp
|
||||
qalculator.hpp qalculator.cpp
|
||||
zutils.hpp zutils.cpp
|
||||
RESOURCES
|
||||
"${SETTINGS_INDEX_JSON}"
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
|
||||
+39
-26
@@ -10,23 +10,28 @@ Q_LOGGING_CATEGORY(lcAppDb, "ZShell.appdb", QtInfoMsg)
|
||||
namespace ZShell {
|
||||
|
||||
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_entry(entry)
|
||||
, m_frequency(frequency) {
|
||||
: QObject(parent), m_entry(entry), m_frequency(frequency) {
|
||||
const auto mo = m_entry->metaObject();
|
||||
const auto tmo = &AppEntry::staticMetaObject;
|
||||
|
||||
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 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]() {
|
||||
m_entry = nullptr;
|
||||
deleteLater();
|
||||
});
|
||||
m_entry = nullptr;
|
||||
deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QObject* AppEntry::entry() const {
|
||||
@@ -118,7 +123,9 @@ AppDb::AppDb(QObject* parent)
|
||||
db.open();
|
||||
|
||||
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 {
|
||||
@@ -145,7 +152,9 @@ void AppDb::setPath(const QString& path) {
|
||||
db.open();
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -183,7 +192,9 @@ void AppDb::setFavoriteApps(const QStringList& favApps) {
|
||||
if (re.isValid()) {
|
||||
m_favoriteAppsRegex << re;
|
||||
} 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 {
|
||||
if (original.startsWith('^') && original.endsWith('$'))
|
||||
return original;
|
||||
if (original.startsWith('^') && original.endsWith('$')) return original;
|
||||
|
||||
const QString escaped = QRegularExpression::escape(original);
|
||||
return QStringLiteral("^%1$").arg(escaped);
|
||||
@@ -206,9 +216,10 @@ void AppDb::incrementFrequency(const QString& id) {
|
||||
auto db = QSqlDatabase::database(m_uuid);
|
||||
QSqlQuery query(db);
|
||||
|
||||
query.prepare("INSERT INTO frequencies (id, frequency) "
|
||||
"VALUES (:id, 1) "
|
||||
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
||||
query.prepare(
|
||||
"INSERT INTO frequencies (id, frequency) "
|
||||
"VALUES (:id, 1) "
|
||||
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
||||
query.bindValue(":id", id);
|
||||
query.exec();
|
||||
|
||||
@@ -221,7 +232,8 @@ void AppDb::incrementFrequency(const QString& id) {
|
||||
emit appsChanged();
|
||||
}
|
||||
} 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;
|
||||
favSet.reserve(m_sortedApps.size());
|
||||
for (const auto* app : std::as_const(m_sortedApps)) {
|
||||
if (isFavorite(app))
|
||||
favSet.insert(app->id());
|
||||
if (isFavorite(app)) 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 bIsFav = favSet.contains(b->id());
|
||||
if (aIsFav != bIsFav)
|
||||
return aIsFav;
|
||||
if (aIsFav != bIsFav) return aIsFav;
|
||||
if (a->frequency() != b->frequency())
|
||||
return a->frequency() > b->frequency();
|
||||
return a->name().localeAwareCompare(b->name()) < 0;
|
||||
@@ -293,10 +306,10 @@ void AppDb::updateApps() {
|
||||
dirty = true;
|
||||
auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
|
||||
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
|
||||
if (m_apps.remove(id)) {
|
||||
emit appsChanged();
|
||||
}
|
||||
});
|
||||
if (m_apps.remove(id)) {
|
||||
emit appsChanged();
|
||||
}
|
||||
});
|
||||
m_apps.insert(id, newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-26
@@ -65,11 +65,16 @@ class AppDb : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED)
|
||||
Q_PROPERTY(QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY favoriteAppsChanged REQUIRED)
|
||||
Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(
|
||||
QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged
|
||||
REQUIRED)
|
||||
Q_PROPERTY(
|
||||
QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY
|
||||
favoriteAppsChanged REQUIRED)
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||
|
||||
public:
|
||||
explicit AppDb(QObject* parent = nullptr);
|
||||
@@ -82,36 +87,36 @@ Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||
[[nodiscard]] QObjectList entries() const;
|
||||
void setEntries(const QObjectList& entries);
|
||||
|
||||
[[nodiscard]] QStringList favoriteApps() const;
|
||||
void setFavoriteApps(const QStringList& favApps);
|
||||
[[nodiscard]] QStringList favoriteApps() const;
|
||||
void setFavoriteApps(const QStringList& favApps);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
||||
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
||||
|
||||
Q_INVOKABLE void incrementFrequency(const QString& id);
|
||||
|
||||
signals:
|
||||
void pathChanged();
|
||||
void entriesChanged();
|
||||
void favoriteAppsChanged();
|
||||
void appsChanged();
|
||||
signals:
|
||||
void pathChanged();
|
||||
void entriesChanged();
|
||||
void favoriteAppsChanged();
|
||||
void appsChanged();
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
|
||||
const QString m_uuid;
|
||||
QString m_path;
|
||||
QObjectList m_entries;
|
||||
QStringList m_favoriteApps;
|
||||
QList<QRegularExpression> m_favoriteAppsRegex;
|
||||
QHash<QString, AppEntry*> m_apps;
|
||||
mutable QList<AppEntry*> m_sortedApps;
|
||||
const QString m_uuid;
|
||||
QString m_path;
|
||||
QObjectList m_entries;
|
||||
QStringList m_favoriteApps;
|
||||
QList<QRegularExpression> m_favoriteAppsRegex;
|
||||
QHash<QString, AppEntry*> m_apps;
|
||||
mutable QList<AppEntry*> m_sortedApps;
|
||||
|
||||
QString regexifyString(const QString& original) const;
|
||||
QList<AppEntry*>& getSortedApps() const;
|
||||
bool isFavorite(const AppEntry* app) const;
|
||||
quint32 getFrequency(const QString& id) const;
|
||||
void updateAppFrequencies();
|
||||
void updateApps();
|
||||
QString regexifyString(const QString& original) const;
|
||||
QList<AppEntry*>& getSortedApps() const;
|
||||
bool isFavorite(const AppEntry* app) const;
|
||||
quint32 getFrequency(const QString& id) const;
|
||||
void updateAppFrequencies();
|
||||
void updateApps();
|
||||
};
|
||||
|
||||
} // namespace ZShell
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <qjsprimitivevalue.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qfile.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
#define ZSHELL_VERSION ""
|
||||
#endif
|
||||
|
||||
+13
-11
@@ -12,18 +12,18 @@ class ZUtils : public QObject {
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(QString version READ version CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
|
||||
Q_PROPERTY(QString version READ version CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
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, QJSValue onSaved);
|
||||
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, QJSValue onFailed);
|
||||
// clang-format on
|
||||
public:
|
||||
// clang-format off
|
||||
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, QJSValue onSaved);
|
||||
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, QJSValue onFailed);
|
||||
// clang-format on
|
||||
|
||||
Q_INVOKABLE static bool copyFile(
|
||||
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 QString settingsIndex();
|
||||
|
||||
[[nodiscard]] QString version() const;
|
||||
[[nodiscard]] QString qtVersion() const;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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())
|
||||
Reference in New Issue
Block a user