mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-23 19:54:02 -04:00
media: change player style
theme: restore layer outline option launcher/theme: tweaks and fix icon service
This commit is contained in:
+1
-1
@@ -83,7 +83,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260918210430-d03946da44cd
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260922103155-c6cc9c3c32ba
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260918210430-d03946da44cd h1:8Pa1/uo973yP2TkjBeBEdVY20hwlCfwQMq5D3bFOf7U=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260918210430-d03946da44cd/go.mod h1:H6yEdNZ+JJHpBSpvAEJVfXWKhIrttydVU3s2JLyTvQI=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260922103155-c6cc9c3c32ba h1:Basmkwiyh8ZAOXpweOw/lu6mEV4mmZl0vK+WPhZPSqA=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20260922103155-c6cc9c3c32ba/go.mod h1:H6yEdNZ+JJHpBSpvAEJVfXWKhIrttydVU3s2JLyTvQI=
|
||||
github.com/AvengeMedia/dgop v1.6.3-0.20260920163359-59c7d81ae3b9 h1:ssoiAng7ng500BgtZa1riJH6kKudQVM8Uk9a9kF7mDA=
|
||||
github.com/AvengeMedia/dgop v1.6.3-0.20260920163359-59c7d81ae3b9/go.mod h1:8TFIP72NLxCbogF2hHU6sXj3RoTbHQW8beeCrisHs/Y=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
|
||||
+1
-1
Submodule dank-qml-common updated: 33558beb06...c9c29deeb3
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790026440,
|
||||
"narHash": "sha256-oNfBLukk6ppC6IvFksPdh17OeNOUEp8WBLduOkIZAas=",
|
||||
"lastModified": 1790081738,
|
||||
"narHash": "sha256-wPe6BGzBoQaH9YtKG+TXgNGxBjgDj+2Qrozg75qLDpc=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "33558beb0630a4bea6718cfcf52bf875c63abc55",
|
||||
"rev": "c9c29deeb3b6436583d20d23d3b210c5522e5292",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-eZBP7+9NLdJ88qizCZ+mTmcQO+FHthqQSmNtUErfKv4=";
|
||||
vendorHash = "sha256-sWvntuwG8BkVykveWdda5aTiXggB7uhf57ufReb4PWk=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ Singleton {
|
||||
readonly property color notificationFloatingSurface: notificationForegroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
||||
readonly property color notificationFloatingSurfaceHigh: notificationForegroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
||||
readonly property color notificationNestedSurface: notificationFloatingSurfaceHigh
|
||||
readonly property real blurLayerOutlineOpacity: Math.max(0, Math.min(1, typeof SettingsData === "undefined" ? 0.12 : (SettingsData.blurLayerOutlineOpacity ?? 0.12)))
|
||||
readonly property real blurLayerOutlineOpacity: Math.max(0, Math.min(1, typeof SettingsData === "undefined" ? 0 : (SettingsData.blurLayerOutlineOpacity ?? 0)))
|
||||
readonly property real layerOutlineOpacity: blurLayerOutlineOpacity
|
||||
readonly property int layerOutlineWidth: layerOutlineOpacity > 0 ? 1 : 0
|
||||
readonly property real floatingWindowFieldAlpha: floatingWindowForegroundAlpha
|
||||
|
||||
@@ -392,7 +392,7 @@ var LOCAL_SPEC = {
|
||||
coerce: Util.percentToUnit
|
||||
},
|
||||
blurLayerOutlineOpacity: {
|
||||
def: 0.12,
|
||||
def: 0,
|
||||
coerce: Util.percentToUnit
|
||||
},
|
||||
focusRingEnabled: {
|
||||
|
||||
@@ -253,7 +253,6 @@ Item {
|
||||
PolkitService.polkitAvailable;
|
||||
DisplayConfigState.hasOutputBackend;
|
||||
PortalService.systemColorScheme;
|
||||
IconThemeService.revision;
|
||||
DesktopService.isSystemd;
|
||||
TrashService.count;
|
||||
WallpaperCyclingService.cyclingActive;
|
||||
|
||||
@@ -532,7 +532,7 @@ DankModal {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
border.width: Theme.layerOutlineWidth
|
||||
visible: root.showTargetData && root.targetData.length > 0
|
||||
|
||||
StyledText {
|
||||
|
||||
@@ -220,6 +220,8 @@ DankModal {
|
||||
height: 56
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: requestType === "confirm" || requestType === "display-passkey"
|
||||
|
||||
Column {
|
||||
|
||||
@@ -16,6 +16,8 @@ Rectangle {
|
||||
height: Math.round(Theme.fontSizeMedium * 4.2)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -76,6 +76,8 @@ DankFloatingWindow {
|
||||
anchors.bottom: parent.bottom
|
||||
height: Math.round(Theme.fontSizeMedium * 4.5)
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
|
||||
@@ -377,7 +377,7 @@ Item {
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
border.width: Theme.layerOutlineWidth
|
||||
}
|
||||
|
||||
contentItem: Column {
|
||||
|
||||
@@ -83,8 +83,8 @@ ClippingRectangle {
|
||||
anchors.margins: Theme.spacingL
|
||||
radius: Theme.cornerRadiusL
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineVariant
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -108,8 +108,8 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.color: Theme.outlineVariant
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -121,6 +121,8 @@ Rectangle {
|
||||
width: parent?.width ?? Theme.fieldDefaultWidth
|
||||
height: expanded && hasActions ? Theme.listItemHeight : 0
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
clip: true
|
||||
|
||||
@@ -19,8 +19,8 @@ Rectangle {
|
||||
radius: Theme.cornerRadiusM
|
||||
clip: true
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.color: Theme.outlineVariant
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
onEntryChanged: reloadPreview()
|
||||
Component.onCompleted: reloadPreview()
|
||||
|
||||
@@ -162,13 +162,6 @@ DankListItem {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankKeycap {
|
||||
text: "↵"
|
||||
textColor: root.contentColor
|
||||
visible: root.isSelected
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Image {
|
||||
width: LauncherMetrics.previewWidth
|
||||
height: LauncherMetrics.previewHeight
|
||||
|
||||
@@ -54,6 +54,8 @@ Rectangle {
|
||||
|
||||
radius: Theme.cornerRadiusL
|
||||
color: externalHighlight ? "transparent" : isSelected ? Theme.selectedContainer : Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: externalHighlight ? 0 : Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -89,16 +91,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
DankKeycap {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXS
|
||||
text: "↵"
|
||||
textColor: root.contentColor
|
||||
visible: root.isSelected
|
||||
z: 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: !root.imageTile
|
||||
|
||||
@@ -1329,7 +1329,9 @@ Item {
|
||||
newSections = existingNonFile.concat(fileSections);
|
||||
}
|
||||
newSections.sort(function (a, b) {
|
||||
return a.priority - b.priority;
|
||||
if (a.priority !== b.priority)
|
||||
return a.priority - b.priority;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
_applyHighlights(newSections, searchQuery);
|
||||
flatModel = Scorer.flattenSections(newSections);
|
||||
@@ -1713,7 +1715,9 @@ Item {
|
||||
}
|
||||
|
||||
baseDefs.sort(function (a, b) {
|
||||
return a.priority - b.priority;
|
||||
if (a.priority !== b.priority)
|
||||
return a.priority - b.priority;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
return baseDefs;
|
||||
}
|
||||
@@ -1930,7 +1934,9 @@ Item {
|
||||
return null;
|
||||
|
||||
sectionsData.sort(function (a, b) {
|
||||
return a.priority - b.priority;
|
||||
if (a.priority !== b.priority)
|
||||
return a.priority - b.priority;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
return sectionsData;
|
||||
}
|
||||
|
||||
@@ -4,72 +4,72 @@ function getFileIcon(filename) {
|
||||
var ext = filename.lastIndexOf(".") > 0 ? filename.substring(filename.lastIndexOf(".") + 1).toLowerCase() : "";
|
||||
|
||||
switch (ext) {
|
||||
case "pdf":
|
||||
return "picture_as_pdf";
|
||||
case "doc":
|
||||
case "docx":
|
||||
case "odt":
|
||||
return "description";
|
||||
case "xls":
|
||||
case "xlsx":
|
||||
case "ods":
|
||||
return "table_chart";
|
||||
case "ppt":
|
||||
case "pptx":
|
||||
case "odp":
|
||||
return "slideshow";
|
||||
case "txt":
|
||||
case "md":
|
||||
case "rst":
|
||||
return "article";
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "png":
|
||||
case "gif":
|
||||
case "svg":
|
||||
case "webp":
|
||||
return "image";
|
||||
case "mp3":
|
||||
case "wav":
|
||||
case "flac":
|
||||
case "ogg":
|
||||
return "audio_file";
|
||||
case "mp4":
|
||||
case "mkv":
|
||||
case "avi":
|
||||
case "webm":
|
||||
return "video_file";
|
||||
case "zip":
|
||||
case "tar":
|
||||
case "gz":
|
||||
case "7z":
|
||||
case "rar":
|
||||
return "folder_zip";
|
||||
case "js":
|
||||
case "ts":
|
||||
case "py":
|
||||
case "rs":
|
||||
case "go":
|
||||
case "java":
|
||||
case "c":
|
||||
case "cpp":
|
||||
case "h":
|
||||
return "code";
|
||||
case "html":
|
||||
case "css":
|
||||
case "htm":
|
||||
return "web";
|
||||
case "json":
|
||||
case "xml":
|
||||
case "yaml":
|
||||
case "yml":
|
||||
return "data_object";
|
||||
case "sh":
|
||||
case "bash":
|
||||
case "zsh":
|
||||
return "terminal";
|
||||
default:
|
||||
return "insert_drive_file";
|
||||
case "pdf":
|
||||
return "picture_as_pdf";
|
||||
case "doc":
|
||||
case "docx":
|
||||
case "odt":
|
||||
return "description";
|
||||
case "xls":
|
||||
case "xlsx":
|
||||
case "ods":
|
||||
return "table_chart";
|
||||
case "ppt":
|
||||
case "pptx":
|
||||
case "odp":
|
||||
return "slideshow";
|
||||
case "txt":
|
||||
case "md":
|
||||
case "rst":
|
||||
return "article";
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "png":
|
||||
case "gif":
|
||||
case "svg":
|
||||
case "webp":
|
||||
return "image";
|
||||
case "mp3":
|
||||
case "wav":
|
||||
case "flac":
|
||||
case "ogg":
|
||||
return "audio_file";
|
||||
case "mp4":
|
||||
case "mkv":
|
||||
case "avi":
|
||||
case "webm":
|
||||
return "video_file";
|
||||
case "zip":
|
||||
case "tar":
|
||||
case "gz":
|
||||
case "7z":
|
||||
case "rar":
|
||||
return "folder_zip";
|
||||
case "js":
|
||||
case "ts":
|
||||
case "py":
|
||||
case "rs":
|
||||
case "go":
|
||||
case "java":
|
||||
case "c":
|
||||
case "cpp":
|
||||
case "h":
|
||||
return "code";
|
||||
case "html":
|
||||
case "css":
|
||||
case "htm":
|
||||
return "web";
|
||||
case "json":
|
||||
case "xml":
|
||||
case "yaml":
|
||||
case "yml":
|
||||
return "data_object";
|
||||
case "sh":
|
||||
case "bash":
|
||||
case "zsh":
|
||||
return "terminal";
|
||||
default:
|
||||
return "insert_drive_file";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,19 +116,13 @@ function classifyAppSource(app) {
|
||||
if (cmd0 === "flatpak" || exec.indexOf("flatpak run ") !== -1)
|
||||
return "flatpak";
|
||||
|
||||
if (cmd0 === "snap"
|
||||
|| exec.indexOf("bamf_desktop_file_hint=") !== -1
|
||||
|| exec.indexOf("/snap/bin/") !== -1
|
||||
|| exec.indexOf("/snap/core") !== -1
|
||||
|| exec.indexOf("snap run ") === 0)
|
||||
if (cmd0 === "snap" || exec.indexOf("bamf_desktop_file_hint=") !== -1 || exec.indexOf("/snap/bin/") !== -1 || exec.indexOf("/snap/core") !== -1 || exec.indexOf("snap run ") === 0)
|
||||
return "snap";
|
||||
|
||||
if (/\.appimage(\s|$|")/i.test(execRaw) || id.indexOf("appimagekit_") === 0)
|
||||
return "appimage";
|
||||
|
||||
if (exec.indexOf("/nix/store/") !== -1
|
||||
|| exec.indexOf("/run/current-system/sw/") !== -1
|
||||
|| exec.indexOf("/etc/profiles/per-user/") !== -1)
|
||||
if (exec.indexOf("/nix/store/") !== -1 || exec.indexOf("/run/current-system/sw/") !== -1 || exec.indexOf("/etc/profiles/per-user/") !== -1)
|
||||
return "nix";
|
||||
|
||||
return "system";
|
||||
@@ -174,7 +168,9 @@ function sortPluginIdsByOrder(pluginIds, order) {
|
||||
return pluginIds.slice().sort(function (a, b) {
|
||||
var aOrder = orderMap[a] !== undefined ? orderMap[a] : 9999;
|
||||
var bOrder = orderMap[b] !== undefined ? orderMap[b] : 9999;
|
||||
return aOrder - bOrder;
|
||||
if (aOrder !== bOrder)
|
||||
return aOrder - bOrder;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -187,7 +183,9 @@ function sortPluginsOrdered(plugins, order) {
|
||||
return plugins.sort(function (a, b) {
|
||||
var aOrder = orderMap[a.id] !== undefined ? orderMap[a.id] : 9999;
|
||||
var bOrder = orderMap[b.id] !== undefined ? orderMap[b.id] : 9999;
|
||||
return aOrder - bOrder;
|
||||
if (aOrder !== bOrder)
|
||||
return aOrder - bOrder;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -196,8 +194,17 @@ function parseFileSearchPrefix(query) {
|
||||
return null;
|
||||
var rest = query.substring(1);
|
||||
if (rest === "d" || rest.startsWith("d ") || rest.startsWith("d\t"))
|
||||
return { type: "dir", query: rest.substring(1).trim() };
|
||||
return {
|
||||
type: "dir",
|
||||
query: rest.substring(1).trim()
|
||||
};
|
||||
if (rest === "f" || rest.startsWith("f ") || rest.startsWith("f\t"))
|
||||
return { type: "file", query: rest.substring(1).trim() };
|
||||
return { type: null, query: rest.trim() };
|
||||
return {
|
||||
type: "file",
|
||||
query: rest.substring(1).trim()
|
||||
};
|
||||
return {
|
||||
type: null,
|
||||
query: rest.trim()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -330,6 +330,8 @@ FocusScope {
|
||||
|
||||
visible: !(root.parentModal?.frameOwnsConnectedChrome ?? false) && !Theme.blurLayersActive
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.windowRadius
|
||||
}
|
||||
DankFilterChips {
|
||||
@@ -370,13 +372,6 @@ FocusScope {
|
||||
color: Theme.onSurfaceVariant
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "↵ " + I18n.tr("Open")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.onSurfaceVariant
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Tab", "keyboard tab key name", true) + " " + I18n.tr("Actions", "noun, launcher footer hint after the tab key, also section label")
|
||||
|
||||
@@ -329,6 +329,8 @@ Item {
|
||||
visible: delegateRoot.visible
|
||||
opacity: delegateRoot.opacity
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.groupedListInnerRadius
|
||||
topLeftRadius: delegateRoot.modelData?.firstInGroup ? Theme.groupedListOuterRadius : radius
|
||||
topRightRadius: topLeftRadius
|
||||
@@ -417,6 +419,8 @@ Item {
|
||||
visible: delegateRoot.visible
|
||||
opacity: delegateRoot.opacity
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.cornerRadiusL
|
||||
}
|
||||
|
||||
|
||||
@@ -18,180 +18,195 @@ const Weights = {
|
||||
file: 800,
|
||||
action: 600
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function tokenize(text) {
|
||||
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function (w) { return w.length > 0 })
|
||||
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function (w) {
|
||||
return w.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
function hasWordBoundaryMatch(text, query) {
|
||||
var textWords = tokenize(text)
|
||||
var queryWords = tokenize(query)
|
||||
var textWords = tokenize(text);
|
||||
var queryWords = tokenize(query);
|
||||
|
||||
if (queryWords.length === 0) return false
|
||||
if (queryWords.length > textWords.length) return false
|
||||
if (queryWords.length === 0)
|
||||
return false;
|
||||
if (queryWords.length > textWords.length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
|
||||
var allMatch = true
|
||||
var allMatch = true;
|
||||
for (var j = 0; j < queryWords.length; j++) {
|
||||
if (!textWords[i + j].startsWith(queryWords[j])) {
|
||||
allMatch = false
|
||||
break
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allMatch) return true
|
||||
if (allMatch)
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
function levenshteinDistance(s1, s2) {
|
||||
var len1 = s1.length
|
||||
var len2 = s2.length
|
||||
var prev = new Array(len2 + 1)
|
||||
var curr = new Array(len2 + 1)
|
||||
var len1 = s1.length;
|
||||
var len2 = s2.length;
|
||||
var prev = new Array(len2 + 1);
|
||||
var curr = new Array(len2 + 1);
|
||||
|
||||
for (var j = 0; j <= len2; j++)
|
||||
prev[j] = j
|
||||
prev[j] = j;
|
||||
|
||||
for (var i = 1; i <= len1; i++) {
|
||||
curr[0] = i
|
||||
curr[0] = i;
|
||||
for (var j = 1; j <= len2; j++) {
|
||||
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1
|
||||
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost)
|
||||
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
||||
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
||||
}
|
||||
var tmp = prev
|
||||
prev = curr
|
||||
curr = tmp
|
||||
var tmp = prev;
|
||||
prev = curr;
|
||||
curr = tmp;
|
||||
}
|
||||
return prev[len2]
|
||||
return prev[len2];
|
||||
}
|
||||
|
||||
function fuzzyScore(text, query) {
|
||||
var maxDistance = query.length === 3 ? 1 : query.length <= 6 ? 2 : 3
|
||||
var bestScore = 0
|
||||
var maxDistance = query.length === 3 ? 1 : query.length <= 6 ? 2 : 3;
|
||||
var bestScore = 0;
|
||||
|
||||
if (Math.abs(text.length - query.length) <= maxDistance) {
|
||||
var distance = levenshteinDistance(text, query)
|
||||
var distance = levenshteinDistance(text, query);
|
||||
if (distance <= maxDistance) {
|
||||
var maxLen = Math.max(text.length, query.length)
|
||||
bestScore = 1 - (distance / maxLen)
|
||||
var maxLen = Math.max(text.length, query.length);
|
||||
bestScore = 1 - (distance / maxLen);
|
||||
}
|
||||
}
|
||||
|
||||
var words = tokenize(text)
|
||||
var words = tokenize(text);
|
||||
for (var i = 0; i < words.length && bestScore < 0.8; i++) {
|
||||
if (Math.abs(words[i].length - query.length) > maxDistance) continue
|
||||
var wordDistance = levenshteinDistance(words[i], query)
|
||||
if (Math.abs(words[i].length - query.length) > maxDistance)
|
||||
continue;
|
||||
var wordDistance = levenshteinDistance(words[i], query);
|
||||
if (wordDistance <= maxDistance) {
|
||||
var wordMaxLen = Math.max(words[i].length, query.length)
|
||||
var score = 1 - (wordDistance / wordMaxLen)
|
||||
bestScore = Math.max(bestScore, score)
|
||||
var wordMaxLen = Math.max(words[i].length, query.length);
|
||||
var score = 1 - (wordDistance / wordMaxLen);
|
||||
bestScore = Math.max(bestScore, score);
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
function calculateTextScore(name, query) {
|
||||
if (name === query) return Weights.exactMatch
|
||||
if (name.startsWith(query)) return Weights.prefixMatch
|
||||
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
||||
if (name.includes(query)) return Weights.substring
|
||||
if (name === query)
|
||||
return Weights.exactMatch;
|
||||
if (name.startsWith(query))
|
||||
return Weights.prefixMatch;
|
||||
if (hasWordBoundaryMatch(name, query))
|
||||
return Weights.wordBoundary;
|
||||
if (name.includes(query))
|
||||
return Weights.substring;
|
||||
|
||||
if (query.length >= 3) {
|
||||
var fs = fuzzyScore(name, query)
|
||||
if (fs > 0) return fs * Weights.fuzzy
|
||||
var fs = fuzzyScore(name, query);
|
||||
if (fs > 0)
|
||||
return fs * Weights.fuzzy;
|
||||
}
|
||||
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
function score(item, query, frecencyData) {
|
||||
var typeBonus = Weights.typeBonus[item.type] || 0
|
||||
var typeBonus = Weights.typeBonus[item.type] || 0;
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
var usageCount = frecencyData ? frecencyData.usageCount : 0
|
||||
return typeBonus + (usageCount * 100)
|
||||
var usageCount = frecencyData ? frecencyData.usageCount : 0;
|
||||
return typeBonus + (usageCount * 100);
|
||||
}
|
||||
|
||||
var name = (item.name || "").toLowerCase()
|
||||
var q = query.toLowerCase()
|
||||
var name = (item.name || "").toLowerCase();
|
||||
var q = query.toLowerCase();
|
||||
|
||||
var textScore = calculateTextScore(name, q)
|
||||
var textScore = calculateTextScore(name, q);
|
||||
|
||||
if (textScore === 0 && item.subtitle) {
|
||||
var subtitleScore = calculateTextScore(item.subtitle.toLowerCase(), q)
|
||||
textScore = subtitleScore * 0.5
|
||||
var subtitleScore = calculateTextScore(item.subtitle.toLowerCase(), q);
|
||||
textScore = subtitleScore * 0.5;
|
||||
}
|
||||
|
||||
if (textScore === 0 && item.keywords) {
|
||||
for (var i = 0; i < item.keywords.length; i++) {
|
||||
var keywordScore = calculateTextScore(item.keywords[i].toLowerCase(), q)
|
||||
var keywordScore = calculateTextScore(item.keywords[i].toLowerCase(), q);
|
||||
if (keywordScore > 0) {
|
||||
textScore = keywordScore * 0.3
|
||||
break
|
||||
textScore = keywordScore * 0.3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textScore === 0 && item.data && item.data.genericName) {
|
||||
var genericLower = item.data.genericName.toLowerCase()
|
||||
var genericLower = item.data.genericName.toLowerCase();
|
||||
if (genericLower.startsWith(q))
|
||||
textScore = Weights.genericPrefix
|
||||
textScore = Weights.genericPrefix;
|
||||
else if (genericLower.includes(q))
|
||||
textScore = Weights.generic
|
||||
textScore = Weights.generic;
|
||||
}
|
||||
|
||||
if (textScore === 0 && item.id && item.type === "app") {
|
||||
var id = item.id.toLowerCase().replace(/\.desktop$/, "")
|
||||
var id = item.id.toLowerCase().replace(/\.desktop$/, "");
|
||||
if (id.includes(q)) {
|
||||
textScore = Weights.id
|
||||
textScore = Weights.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (textScore === 0) return 0
|
||||
if (textScore === 0)
|
||||
return 0;
|
||||
|
||||
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 50, Weights.frecency) : 0
|
||||
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 50, Weights.frecency) : 0;
|
||||
|
||||
return textScore + usageBonus + typeBonus
|
||||
return textScore + usageBonus + typeBonus;
|
||||
}
|
||||
|
||||
function scoreItems(items, query, getFrecencyFn) {
|
||||
var scored = []
|
||||
var scored = [];
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i]
|
||||
var itemScore
|
||||
var item = items[i];
|
||||
var itemScore;
|
||||
|
||||
if (item._preScored !== undefined && (query || item._preScored > 900)) {
|
||||
itemScore = item._preScored
|
||||
itemScore = item._preScored;
|
||||
} else {
|
||||
var frecencyData = getFrecencyFn ? getFrecencyFn(item) : null
|
||||
itemScore = score(item, query, frecencyData)
|
||||
var frecencyData = getFrecencyFn ? getFrecencyFn(item) : null;
|
||||
itemScore = score(item, query, frecencyData);
|
||||
}
|
||||
|
||||
if (itemScore > 0 || !query || query.length === 0) {
|
||||
scored.push({
|
||||
item: item,
|
||||
score: itemScore
|
||||
})
|
||||
score: itemScore,
|
||||
order: scored.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scored.sort(function (a, b) {
|
||||
return b.score - a.score
|
||||
})
|
||||
if (a.score !== b.score)
|
||||
return b.score - a.score;
|
||||
return a.order - b.order;
|
||||
});
|
||||
|
||||
return scored
|
||||
return scored;
|
||||
}
|
||||
|
||||
function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSection) {
|
||||
var sections = {}
|
||||
var result = []
|
||||
var limit = maxPerSection || 50
|
||||
var sections = {};
|
||||
var result = [];
|
||||
var limit = maxPerSection || 50;
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var sectionId = sectionOrder[i].id
|
||||
var sectionId = sectionOrder[i].id;
|
||||
sections[sectionId] = {
|
||||
id: sectionId,
|
||||
title: sectionOrder[i].title,
|
||||
@@ -200,53 +215,53 @@ function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSec
|
||||
items: [],
|
||||
collapsed: false,
|
||||
flatStartIndex: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < scoredItems.length; i++) {
|
||||
var scoredItem = scoredItems[i]
|
||||
var item = scoredItem.item
|
||||
var sectionId = item.section || "apps"
|
||||
var scoredItem = scoredItems[i];
|
||||
var item = scoredItem.item;
|
||||
var sectionId = item.section || "apps";
|
||||
|
||||
if (sections[sectionId] && sections[sectionId].items.length < limit) {
|
||||
sections[sectionId].items.push(item)
|
||||
sections[sectionId].items.push(item);
|
||||
} else if (sections["apps"] && sections["apps"].items.length < limit) {
|
||||
sections["apps"].items.push(item)
|
||||
sections["apps"].items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var section = sections[sectionOrder[i].id]
|
||||
var section = sections[sectionOrder[i].id];
|
||||
if (section && section.items.length > 0) {
|
||||
if (sortAlphabetically && section.id === "apps") {
|
||||
section.items.sort(function (a, b) {
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
}
|
||||
result.push(section)
|
||||
result.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
function flattenSections(sections) {
|
||||
var flat = []
|
||||
flat._sectionBounds = null
|
||||
var bounds = {}
|
||||
var flat = [];
|
||||
flat._sectionBounds = null;
|
||||
var bounds = {};
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var section = sections[i]
|
||||
var section = sections[i];
|
||||
|
||||
flat.push({
|
||||
isHeader: true,
|
||||
section: section,
|
||||
sectionId: section.id,
|
||||
sectionIndex: i
|
||||
})
|
||||
});
|
||||
|
||||
var itemStart = flat.length
|
||||
section.flatStartIndex = itemStart
|
||||
var itemStart = flat.length;
|
||||
section.flatStartIndex = itemStart;
|
||||
|
||||
if (!section.collapsed) {
|
||||
for (var j = 0; j < section.items.length; j++) {
|
||||
@@ -256,21 +271,21 @@ function flattenSections(sections) {
|
||||
sectionId: section.id,
|
||||
sectionIndex: i,
|
||||
indexInSection: j
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var itemEnd = flat.length - 1
|
||||
var itemCount = flat.length - itemStart
|
||||
var itemEnd = flat.length - 1;
|
||||
var itemCount = flat.length - itemStart;
|
||||
if (itemCount > 0) {
|
||||
bounds[section.id] = {
|
||||
start: itemStart,
|
||||
end: itemEnd,
|
||||
count: itemCount
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
flat._sectionBounds = bounds
|
||||
return flat
|
||||
flat._sectionBounds = bounds;
|
||||
return flat;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ LauncherTile {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
ScreencopyView {
|
||||
id: screencopyView
|
||||
|
||||
@@ -89,6 +89,8 @@ DankModal {
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -172,6 +172,8 @@ Item {
|
||||
height: keybindsGrid.height + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
readonly property bool useTwoColumns: width > 500
|
||||
readonly property int columnCount: useTwoColumns ? 2 : 1
|
||||
@@ -251,6 +253,8 @@ Item {
|
||||
height: noKeybindsColumn.height + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: !root.hasKeybinds
|
||||
|
||||
Column {
|
||||
|
||||
@@ -290,6 +290,8 @@ Item {
|
||||
anchors.rightMargin: Theme.spacingXL
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
|
||||
@@ -16,6 +16,8 @@ Rectangle {
|
||||
height: Math.round(Theme.fontSizeMedium * 6.4)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -163,6 +163,8 @@ DankFloatingWindow {
|
||||
height: indicatorHeight
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
id: pageIndicatorRow
|
||||
@@ -224,6 +226,8 @@ DankFloatingWindow {
|
||||
anchors.bottom: parent.bottom
|
||||
height: Math.round(Theme.fontSizeMedium * 4.5)
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
|
||||
@@ -14,6 +14,8 @@ Rectangle {
|
||||
height: Math.round(Theme.fontSizeMedium * 3.1)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -16,6 +16,8 @@ Rectangle {
|
||||
height: Math.round(Theme.fontSizeMedium * 4.5)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -17,8 +17,8 @@ Rectangle {
|
||||
height: Math.round(Theme.fontSizeMedium * 5)
|
||||
radius: Theme.cornerRadius
|
||||
color: bgColor
|
||||
border.width: selected ? 2 : 0
|
||||
border.color: selected ? iconColor : Theme.withAlpha(iconColor, 0)
|
||||
border.width: selected ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: selected ? iconColor : Theme.outlineMedium
|
||||
scale: mouseArea.pressed ? 0.97 : 1
|
||||
|
||||
Behavior on scale {
|
||||
|
||||
@@ -327,6 +327,8 @@ DankModal {
|
||||
height: 56
|
||||
radius: Theme.cornerRadius
|
||||
color: muxModal.selectedIndex === -1 ? Theme.selectedContainer : (newMouse.containsMouse ? Theme.foregroundColor(Theme.chipSurface, true) : Theme.floatingWindowNestedSurface)
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -404,6 +406,8 @@ DankModal {
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: muxModal.selectedIndex === index ? Theme.selectedContainer : (sessionMouse.containsMouse ? Theme.cardSurface : Theme.withAlpha(Theme.cardSurface, 0))
|
||||
border.width: muxModal.selectedIndex === index || sessionMouse.containsMouse ? Theme.layerOutlineWidth : 0
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
MouseArea {
|
||||
id: sessionMouse
|
||||
|
||||
@@ -95,8 +95,8 @@ DankModal {
|
||||
height: parent.height - 140
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
clip: true
|
||||
|
||||
DankFlickable {
|
||||
|
||||
@@ -196,8 +196,8 @@ DankModal {
|
||||
return Theme.floatingWindowNestedSurface;
|
||||
}
|
||||
|
||||
border.color: isActive ? Theme.primary : (isSelected ? Theme.withAlpha(Theme.primary, 0.5) : Theme.withAlpha(Theme.primary, 0))
|
||||
border.width: (isActive || isSelected) ? 2 : 0
|
||||
border.color: isActive ? Theme.primary : (isSelected ? Theme.withAlpha(Theme.primary, 0.5) : Theme.outlineMedium)
|
||||
border.width: (isActive || isSelected) ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
|
||||
// Shortcut Key Badge on Top-Right Corner
|
||||
Rectangle {
|
||||
|
||||
@@ -325,6 +325,8 @@ DankFloatingWindow {
|
||||
width: parent.width
|
||||
height: showBanner ? bannerContent.implicitHeight + Theme.spacingM * 2 : 0
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: showBanner
|
||||
clip: true
|
||||
|
||||
|
||||
@@ -160,6 +160,8 @@ DankModal {
|
||||
height: bodyCol.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadiusL
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -79,6 +79,8 @@ Item {
|
||||
height: menu.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
x: Math.max(Theme.spacingS, Math.min(root.anchorX + root.anchorWidth - width, root.width - width - Theme.spacingS))
|
||||
y: root.anchorY - height - Theme.spacingS < Theme.spacingS ? root.anchorY + root.anchorHeight + Theme.spacingS : root.anchorY - height - Theme.spacingS
|
||||
opacity: root.visible ? 1 : 0
|
||||
|
||||
@@ -134,6 +134,8 @@ Item {
|
||||
height: column.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
opacity: root._open ? 1 : 0
|
||||
scale: root._open ? 1 : CcMetrics.popupEnterScale
|
||||
transformOrigin: I18n.isRtl ? Item.TopLeft : Item.TopRight
|
||||
|
||||
@@ -82,6 +82,8 @@ Item {
|
||||
height: column.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
opacity: root.shown ? 1 : 0
|
||||
scale: root.shown ? 1 : CcMetrics.popupEnterScale
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
radius: root.bodyRadius
|
||||
color: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: root.tall
|
||||
}
|
||||
|
||||
@@ -63,6 +65,8 @@ Item {
|
||||
y: root.vertical ? parent.height - height : root.tall ? 0 : (parent.height - height) / 2
|
||||
buttonSize: root.actionSize
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
iconName: root.iconName
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
|
||||
@@ -151,6 +151,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
radius: root.bodyRadius
|
||||
color: root.bodyColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Behavior on color {
|
||||
enabled: CcMetrics.animationsEnabled && !SettingsData.reduceMotion
|
||||
@@ -221,6 +223,8 @@ Item {
|
||||
height: width
|
||||
radius: root.hasIconBox ? root.iconBoxRadius : 0
|
||||
color: root.hasIconBox ? root.iconBoxColor : "transparent"
|
||||
border.width: root.hasIconBox ? Theme.layerOutlineWidth : 0
|
||||
border.color: Theme.outlineMedium
|
||||
activeFocusOnTab: root.showExpand && root.toggle && root.acceptsInput
|
||||
Accessible.role: root.toggle ? Accessible.CheckBox : Accessible.Button
|
||||
Accessible.name: root.title
|
||||
|
||||
@@ -27,6 +27,8 @@ Rectangle {
|
||||
implicitHeight: CcMetrics.libraryPanelHeight
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -137,6 +137,7 @@ BasePill {
|
||||
round: false
|
||||
checkable: true
|
||||
checked: root._isPlaying
|
||||
iconFilled: false
|
||||
iconName: root._isPlaying ? "pause" : "play_arrow"
|
||||
Accessible.name: root._isPlaying ? I18n.tr("Pause") : I18n.tr("Play")
|
||||
enabled: root.activePlayer?.canTogglePlaying ?? false
|
||||
|
||||
@@ -198,8 +198,12 @@ Singleton {
|
||||
readonly property real mediaArtLowResScale: 0.85
|
||||
readonly property real mediaArtSizeMaterial: 200
|
||||
readonly property real mediaArtRadius: Theme.cornerRadiusXL
|
||||
readonly property real mediaArtSoftRadiusRatio: 0.3
|
||||
readonly property real mediaArtPlaceholderIcon: Theme.buttonHeightM
|
||||
readonly property real mediaSeekbarHeight: 22
|
||||
readonly property real mediaPlayWidthRatio: 1.5
|
||||
readonly property real mediaPlayWidthStep: 0.75
|
||||
readonly property real mediaTransportPressScale: 1.2
|
||||
readonly property real accentSelectedAlpha: 0.2
|
||||
readonly property real groupIdleAlpha: 0.1
|
||||
readonly property int mediaPositionPollInterval: 1000
|
||||
|
||||
@@ -113,6 +113,14 @@ Singleton {
|
||||
{
|
||||
"value": "circle",
|
||||
"text": I18n.tr("Circle")
|
||||
},
|
||||
{
|
||||
"value": "square",
|
||||
"text": I18n.tr("Soft square", "Album artwork shape")
|
||||
},
|
||||
{
|
||||
"value": "slanted",
|
||||
"text": I18n.tr("Slanted", "Album artwork shape")
|
||||
}
|
||||
]), choice("titleFont", I18n.tr("Title font"), MediaOptions.defaultTitleFont, Theme.fontChoices), toggle("seekbar", I18n.tr("Show seekbar"), MediaOptions.defaults.seekbar), toggle("waveProgress", I18n.tr("Wave progress bars"), MediaOptions.defaults.waveProgress), toggle("albumArtBackdrop", I18n.tr("Album art backdrop"), MediaOptions.defaults.albumArtBackdrop), toggle("albumArtAccent", I18n.tr("Use album art accent"), MediaOptions.defaults.albumArtAccent), toggle("animatedArt", I18n.tr("Apple Music animated covers"), MediaOptions.defaults.animatedArt, I18n.tr("Sends the playing artist and album name to Apple")), toggle("lyrics", I18n.tr("Lyrics", "Media player lyrics button"), MediaOptions.defaults.lyrics, I18n.tr("Sends the playing track, artist and album name to enabled lyrics providers"))]
|
||||
},
|
||||
|
||||
@@ -219,11 +219,11 @@ FocusScope {
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
text: root.unsynced ? I18n.tr("Unsynced", "Lyrics have no timestamps") : I18n.tr("Lyrics", "Media player lyrics button")
|
||||
text: I18n.tr("Unsynced", "Lyrics have no timestamps")
|
||||
color: Theme.onSurfaceVariant
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: !root.compact
|
||||
visible: root.unsynced && !root.compact
|
||||
}
|
||||
|
||||
DankListView {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Shapes
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Modules.DankDash
|
||||
import "../../../DankCommon/Widgets/MaterialShapes.js" as Shapes
|
||||
|
||||
ClippingRectangle {
|
||||
id: root
|
||||
|
||||
property string artStyle: "rounded"
|
||||
readonly property real contentRadius: artStyle === "slanted" ? width * DashMetrics.mediaArtSoftRadiusRatio * Theme.shapeScale : radius
|
||||
|
||||
radius: {
|
||||
switch (artStyle) {
|
||||
case "circle":
|
||||
return width / 2;
|
||||
case "square":
|
||||
return width * DashMetrics.mediaArtSoftRadiusRatio * Theme.shapeScale;
|
||||
case "slanted":
|
||||
return 0;
|
||||
}
|
||||
return DashMetrics.mediaArtRadius;
|
||||
}
|
||||
color: "transparent"
|
||||
antialiasing: true
|
||||
layer.enabled: artStyle === "slanted"
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: shapeMask.item
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: shapeMask
|
||||
anchors.fill: parent
|
||||
active: root.layer.enabled
|
||||
visible: false
|
||||
|
||||
sourceComponent: Item {
|
||||
layer.enabled: true
|
||||
|
||||
Shape {
|
||||
id: shape
|
||||
|
||||
readonly property real scaleX: boundingRect.width > 0 ? root.width / boundingRect.width : 1
|
||||
readonly property real scaleY: boundingRect.height > 0 ? root.height / boundingRect.height : 1
|
||||
|
||||
x: -boundingRect.x * scaleX
|
||||
y: -boundingRect.y * scaleY
|
||||
width: root.width
|
||||
height: root.height
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
transform: Scale {
|
||||
xScale: shape.scaleX
|
||||
yScale: shape.scaleY
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
strokeWidth: 0
|
||||
strokeColor: "transparent"
|
||||
fillColor: Theme.withAlpha(Theme.surface, 1)
|
||||
|
||||
PathSvg {
|
||||
path: Shapes.buildPath("slanted", root.width, root.height, Theme.shapeScale === 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Widgets
|
||||
import qs.Widgets
|
||||
import qs.Modules.DankDash
|
||||
|
||||
@@ -12,14 +11,12 @@ Item {
|
||||
property bool showLyrics: true
|
||||
property bool holdArt: false
|
||||
|
||||
readonly property real artRadius: root.player.options?.artStyle === "circle" ? width / 2 : DashMetrics.mediaArtRadius
|
||||
readonly property real artRadius: surface.contentRadius
|
||||
|
||||
ClippingRectangle {
|
||||
MediaArtSurface {
|
||||
id: surface
|
||||
anchors.fill: parent
|
||||
radius: root.artRadius
|
||||
color: "transparent"
|
||||
antialiasing: true
|
||||
artStyle: root.player.options?.artStyle ?? "rounded"
|
||||
|
||||
MediaArtwork {
|
||||
id: art
|
||||
|
||||
@@ -59,6 +59,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
radius: root.backdropRadius
|
||||
color: root.surfaceColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -94,9 +94,10 @@ Item {
|
||||
iconName: AudioService.sinkIcon(AudioService.sink)
|
||||
tooltipText: root.fittedSinkName === root.sinkName ? root.devicesLabel : root.sinkName
|
||||
Accessible.name: root.devicesLabel + ": " + root.sinkName
|
||||
backgroundColor: "transparent"
|
||||
textColor: Theme.onSurfaceVariant
|
||||
horizontalPadding: Theme.spacingS
|
||||
buttonHeight: Theme.buttonHeightXS
|
||||
backgroundColor: MediaAccentService.accentSecondaryContainer
|
||||
textColor: MediaAccentService.onAccentSecondaryContainer
|
||||
horizontalPadding: Theme.spacingM
|
||||
onClicked: root.player.togglePanel("devices")
|
||||
|
||||
MediaSinkWheel {}
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.DankDash
|
||||
import "../../../DankCommon/Common/FocusNavigation.js" as FocusNavigation
|
||||
|
||||
RowLayout {
|
||||
@@ -14,7 +15,8 @@ RowLayout {
|
||||
required property var presentation
|
||||
|
||||
readonly property var focusTargets: [shuffleButton, previousButton, playButton, nextButton, repeatButton]
|
||||
readonly property bool hasControls: !!presentation && (presentation.play || presentation.previous || presentation.next || presentation.shuffle || presentation.repeat)
|
||||
readonly property bool hasControls: !!presentation
|
||||
readonly property var pressSpring: Theme.springPreset("fast", Theme.expressiveDurations.expressiveFastSpatial)
|
||||
|
||||
spacing: Theme.spacingS
|
||||
implicitHeight: Theme.buttonHeightM
|
||||
@@ -38,7 +40,7 @@ RowLayout {
|
||||
id: playButton
|
||||
mediaAction: "play"
|
||||
size: "m"
|
||||
Layout.preferredWidth: Theme.buttonHeightM + Theme.spacingS
|
||||
weight: DashMetrics.mediaPlayWidthRatio + (Number(shuffleButton.visible) + Number(repeatButton.visible)) * DashMetrics.mediaPlayWidthStep
|
||||
variant: "filled"
|
||||
iconSize: Theme.iconSizeLarge
|
||||
containerColor: MediaAccentService.accentContainer
|
||||
@@ -56,15 +58,32 @@ RowLayout {
|
||||
}
|
||||
|
||||
component TransportButton: MediaTransportButton {
|
||||
id: button
|
||||
|
||||
property real weight: 1
|
||||
readonly property real targetWeight: weight * (pressed ? DashMetrics.mediaTransportPressScale : 1)
|
||||
|
||||
player: root.player
|
||||
keepNavigation: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: Theme.minimumTouchTargetSize
|
||||
Layout.preferredWidth: Theme.minimumTouchTargetSize * widthSpring.value
|
||||
Layout.minimumWidth: Theme.iconSizeMedium + Theme.spacingXS * 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
round: false
|
||||
buttonSize: size === "m" ? Theme.buttonHeightM : Theme.minimumTouchTargetSize
|
||||
buttonSize: Theme.buttonHeightM
|
||||
radius: Theme.buttonRadius(width, height, buttonSize, pressed, false)
|
||||
variant: "tonal"
|
||||
containerColor: checked ? MediaAccentService.accentContainer : MediaAccentService.accentSecondaryContainer
|
||||
contentColor: checked ? MediaAccentService.onAccentContainer : MediaAccentService.onAccentSecondaryContainer
|
||||
|
||||
onTargetWeightChanged: widthSpring.retarget(targetWeight)
|
||||
|
||||
SpringMotion {
|
||||
id: widthSpring
|
||||
enabled: root.visible && root.player.live && !Theme.springMotionDisabled
|
||||
stiffness: root.pressSpring.stiffness
|
||||
damping: root.pressSpring.damping
|
||||
Component.onCompleted: snapTo(button.targetWeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ DankIconButton {
|
||||
|
||||
required property var player
|
||||
required property string mediaAction
|
||||
property bool keepNavigation: false
|
||||
|
||||
readonly property var presentation: root.player.presentation
|
||||
readonly property var activePlayer: root.player.activePlayer
|
||||
@@ -18,6 +19,7 @@ DankIconButton {
|
||||
readonly property bool repeatingTrack: root.presentation?.loopState === MprisLoopState.Track
|
||||
|
||||
checkable: root.mediaAction === "shuffle" || root.mediaAction === "play" || root.mediaAction === "repeat"
|
||||
iconFilled: false
|
||||
|
||||
iconName: {
|
||||
switch (root.mediaAction) {
|
||||
@@ -56,11 +58,11 @@ DankIconButton {
|
||||
case "shuffle":
|
||||
return !!root.presentation?.shuffle;
|
||||
case "previous":
|
||||
return !!root.presentation?.previous;
|
||||
return root.keepNavigation ? !!root.presentation : !!root.presentation?.previous;
|
||||
case "play":
|
||||
return !!root.presentation?.play;
|
||||
case "next":
|
||||
return !!root.presentation?.next;
|
||||
return root.keepNavigation ? !!root.presentation : !!root.presentation?.next;
|
||||
case "repeat":
|
||||
return !!root.presentation?.repeat;
|
||||
}
|
||||
@@ -72,7 +74,7 @@ DankIconButton {
|
||||
case "shuffle":
|
||||
return !!root.activePlayer?.shuffleSupported && !!root.activePlayer?.canControl;
|
||||
case "previous":
|
||||
return !!root.activePlayer?.canGoPrevious || !!root.activePlayer?.canSeek;
|
||||
return !!root.activePlayer?.canGoPrevious || (!root.keepNavigation && !!root.activePlayer?.canSeek);
|
||||
case "play":
|
||||
return !!root.activePlayer?.canTogglePlaying;
|
||||
case "next":
|
||||
|
||||
@@ -10,11 +10,9 @@ import "../../../Common/Format.js" as Format
|
||||
MediaChromeBase {
|
||||
id: root
|
||||
|
||||
readonly property bool compact: width < Theme.smallBreakpoint
|
||||
readonly property bool compact: width < Theme.smallBreakpoint || halfWidth < headerHeight + controls.implicitHeight
|
||||
readonly property real halfWidth: Math.max(0, (width - padding * 2 - Theme.spacingXL) / 2)
|
||||
readonly property real bodyHeight: Math.max(0, height - padding * 2)
|
||||
readonly property real paneWidth: artworkSize
|
||||
readonly property real paneOffset: Math.max(0, (width - padding * 2 - paneWidth * 2 - Theme.spacingXL) / 2)
|
||||
// !TODO: expose this variant? playerPaneOpen false gives art beside lyrics, or art alone with hover transport
|
||||
readonly property bool playerPane: root.player.playerPaneOpen
|
||||
readonly property bool controlsColumn: playerPane
|
||||
@@ -26,24 +24,24 @@ MediaChromeBase {
|
||||
readonly property real columnHeight: compact ? artworkSize : Math.max(artworkSize, paneHeight)
|
||||
readonly property real paneHeight: Math.max(artworkSize, headerHeight + controls.implicitHeight)
|
||||
readonly property real paneMinHeight: Math.max(naturalArtSize, headerHeight + controls.implicitHeight)
|
||||
readonly property real headerHeight: sourceRow.height + Theme.spacingL
|
||||
readonly property real headerHeight: sourceRow.height + Theme.spacingS
|
||||
readonly property real renderScale: Window.window?.devicePixelRatio ?? Screen.devicePixelRatio
|
||||
readonly property int artSide: TrackArtService.resolvedArtSide
|
||||
readonly property bool lowResArt: artSide > 0 && artSide < artworkSize * renderScale * DashMetrics.mediaArtLowResRatio
|
||||
|
||||
artSize: DashMetrics.mediaArtSizeDash
|
||||
surfaceColor: DashMetrics.cardColor
|
||||
surfaceColor: Theme.foregroundColor(Theme.hostSurface)
|
||||
baseHeight: Math.max(DashMetrics.tabMinHeight, padding * 2 + (compact ? naturalArtSize + Theme.spacingXL + paneMinHeight : Math.max(naturalArtSize, paneMinHeight)))
|
||||
focusTargets: (viewToggle.visible ? [viewToggle] : []).concat(artTransportLoader.item?.focusTargets ?? [], [playerButton], seekbar.canSeek && seekBlock.visible ? [seekbar] : [], transport.focusTargets, sourceGroup.focusTargets)
|
||||
focusTargets: (viewToggle.visible ? [viewToggle] : []).concat(artTransportLoader.item?.focusTargets ?? [], [playerButton], sourceGroup.focusTargets, transport.focusTargets, seekbar.canSeek && seekBlock.visible ? [seekbar] : [])
|
||||
panelButtons: [playerButton].concat(sourceGroup.panelButtons)
|
||||
inlineVolume: true
|
||||
|
||||
Item {
|
||||
id: artPane
|
||||
|
||||
x: root.compact || !root.splitPanes ? (parent.width - width) / 2 : root.paneOffset + (I18n.isRtl ? root.paneWidth + Theme.spacingXL : 0)
|
||||
x: root.compact || !root.splitPanes ? (parent.width - width) / 2 : I18n.isRtl ? parent.width - width : 0
|
||||
y: root.compact && root.splitPanes ? 0 : (parent.height - height) / 2
|
||||
width: root.compact ? parent.width : root.paneWidth
|
||||
width: root.compact ? parent.width : art.width
|
||||
height: root.columnHeight
|
||||
|
||||
Behavior on x {
|
||||
@@ -57,8 +55,11 @@ MediaChromeBase {
|
||||
|
||||
Item {
|
||||
id: art
|
||||
|
||||
readonly property real scaledSize: root.artworkSize * (root.lowResArt ? DashMetrics.mediaArtLowResScale : 1)
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: root.artworkSize * (root.lowResArt ? DashMetrics.mediaArtLowResScale : 1)
|
||||
width: root.compact ? scaledSize : Math.min(root.artworkSize, Math.max(scaledSize, root.headerHeight + controls.implicitHeight))
|
||||
height: width
|
||||
|
||||
HoverHandler {
|
||||
@@ -191,6 +192,7 @@ MediaChromeBase {
|
||||
|
||||
component ArtTransportButton: MediaTransportButton {
|
||||
player: root.player
|
||||
keepNavigation: true
|
||||
round: true
|
||||
checkable: false
|
||||
buttonSize: Theme.minimumTouchTargetSize
|
||||
@@ -202,9 +204,9 @@ MediaChromeBase {
|
||||
Item {
|
||||
id: contentPane
|
||||
|
||||
x: root.compact ? 0 : root.paneOffset + (I18n.isRtl ? 0 : root.paneWidth + Theme.spacingXL)
|
||||
x: root.compact || I18n.isRtl ? 0 : artPane.width + Theme.spacingXL
|
||||
y: root.compact ? root.artworkSize + Theme.spacingXL : (parent.height - height) / 2
|
||||
width: root.compact ? parent.width : root.paneWidth
|
||||
width: root.compact ? parent.width : parent.width - artPane.width - Theme.spacingXL
|
||||
height: root.compact ? root.paneHeight : root.columnHeight
|
||||
visible: root.splitPanes
|
||||
|
||||
@@ -225,20 +227,22 @@ MediaChromeBase {
|
||||
Column {
|
||||
id: controls
|
||||
visible: root.controlsColumn
|
||||
y: root.headerHeight + (parent.height - root.headerHeight - height) / 2
|
||||
y: root.compact ? parent.height - height : art.y + art.height - height
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: metadata.implicitHeight + Theme.spacingL * 2
|
||||
height: metadata.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusL
|
||||
color: DashMetrics.cardColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Column {
|
||||
id: metadata
|
||||
x: Theme.spacingL
|
||||
y: Theme.spacingL
|
||||
y: Theme.spacingS
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -248,7 +252,7 @@ MediaChromeBase {
|
||||
font: Qt.font({
|
||||
family: titleText.font.family,
|
||||
weight: titleText.font.weight,
|
||||
pixelSize: Theme.fontSizeXXLarge
|
||||
pixelSize: Theme.fontSizeDisplay
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,9 +266,9 @@ MediaChromeBase {
|
||||
|
||||
readonly property real fittedPixelSize: {
|
||||
if (fullSizeTitle.advanceWidth <= width)
|
||||
return Theme.fontSizeXXLarge;
|
||||
const fitted = Math.floor(Theme.fontSizeXXLarge * width / fullSizeTitle.advanceWidth);
|
||||
return Math.max(Theme.fontSizeXLarge, fitted);
|
||||
return Theme.fontSizeDisplay;
|
||||
const fitted = Math.floor(Theme.fontSizeDisplay * width / fullSizeTitle.advanceWidth);
|
||||
return Math.max(Theme.fontSizeXXLarge, fitted);
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
@@ -299,7 +303,8 @@ MediaChromeBase {
|
||||
height: playback.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadiusL
|
||||
color: DashMetrics.cardColor
|
||||
visible: root.hasSeekbar || transport.hasControls
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Column {
|
||||
id: playback
|
||||
@@ -308,6 +313,20 @@ MediaChromeBase {
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
spacing: Theme.spacingS
|
||||
|
||||
MediaSourceGroup {
|
||||
id: sourceGroup
|
||||
width: parent.width
|
||||
player: root.player
|
||||
}
|
||||
|
||||
MediaTransport {
|
||||
id: transport
|
||||
width: parent.width
|
||||
player: root.player
|
||||
presentation: root.presentation
|
||||
visible: hasControls
|
||||
}
|
||||
|
||||
Item {
|
||||
id: seekBlock
|
||||
width: parent.width
|
||||
@@ -316,16 +335,12 @@ MediaChromeBase {
|
||||
LayoutMirroring.enabled: false
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
NumericText {
|
||||
TimeLabel {
|
||||
id: elapsed
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.ceil(reservedWidth)
|
||||
text: Format.formatDuration(seekbar.value * (root.presentation?.length ?? 0))
|
||||
reserveText: duration.text.replace(/\d/g, "8")
|
||||
isMonospace: false
|
||||
color: Theme.onSurfaceVariant
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
MediaSeekbar {
|
||||
@@ -337,41 +352,22 @@ MediaChromeBase {
|
||||
player: root.player
|
||||
}
|
||||
|
||||
NumericText {
|
||||
TimeLabel {
|
||||
id: duration
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.ceil(reservedWidth)
|
||||
text: Format.formatDuration(root.presentation?.length ?? 0)
|
||||
reserveText: text.replace(/\d/g, "8")
|
||||
isMonospace: false
|
||||
color: Theme.onSurfaceVariant
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
MediaTransport {
|
||||
id: transport
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(parent.width, implicitWidth)
|
||||
player: root.player
|
||||
presentation: root.presentation
|
||||
visible: hasControls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaSourceGroup {
|
||||
id: sourceGroup
|
||||
width: parent.width
|
||||
player: root.player
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sourceRow
|
||||
|
||||
y: Math.round((controls.y - height) / 2)
|
||||
y: root.compact ? Math.round((controls.y - height) / 2) : art.y
|
||||
width: parent.width
|
||||
height: Theme.buttonHeightXS
|
||||
|
||||
@@ -409,4 +405,12 @@ MediaChromeBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TimeLabel: NumericText {
|
||||
width: Math.ceil(reservedWidth)
|
||||
isMonospace: false
|
||||
color: Theme.onSurfaceVariant
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.DankDash
|
||||
import qs.Modules.DankDash.Media
|
||||
import "../../../DankCommon/Common/FocusNavigation.js" as FocusNavigation
|
||||
|
||||
Card {
|
||||
@@ -25,7 +26,6 @@ Card {
|
||||
readonly property string artUrl: TrackArtService.resolvedArtUrl
|
||||
readonly property color accent: MediaAccentService.accent
|
||||
readonly property bool showSeekbar: options.seekbar !== false && !tiny
|
||||
readonly property bool circleArt: options.artStyle === "circle"
|
||||
readonly property bool zurvan: options.playerStyle === "zurvan"
|
||||
readonly property bool inlineMetadata: compact && titleLabel.implicitHeight + artistLabel.implicitHeight + trackText.spacing > transport.y - Theme.spacingXS
|
||||
readonly property var playbackFocusTargets: [playButton, previousButton, nextButton].concat(seekbar.canSeek ? [seekbar] : [])
|
||||
@@ -92,15 +92,20 @@ Card {
|
||||
visible: !!root.activePlayer
|
||||
enabled: root.interactive
|
||||
|
||||
MediaArtwork {
|
||||
MediaArtSurface {
|
||||
id: artwork
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: root.showArtwork ? Math.max(0, Math.min(DashMetrics.overviewArtHero, parent.width / 3, seekBlock.y - Theme.spacingM)) : 0
|
||||
height: width
|
||||
cornerRadius: root.circleArt ? width / 2 : DashMetrics.mediaArtRadius
|
||||
artUrl: root.artUrl
|
||||
artStyle: root.options.artStyle ?? "rounded"
|
||||
visible: root.showArtwork
|
||||
|
||||
MediaArtwork {
|
||||
anchors.fill: parent
|
||||
cornerRadius: 0
|
||||
artUrl: root.artUrl
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -188,6 +193,8 @@ Card {
|
||||
round: false
|
||||
checkable: true
|
||||
checked: root.playing
|
||||
iconFilled: false
|
||||
radius: Theme.buttonRadius(width, height, buttonSize, pressed, !root.zurvan && checked)
|
||||
iconName: root.playing ? "pause" : "play_arrow"
|
||||
Accessible.name: root.playing ? I18n.tr("Pause") : I18n.tr("Play")
|
||||
enabled: !!root.activePlayer?.canTogglePlaying
|
||||
@@ -204,6 +211,7 @@ Card {
|
||||
width: transport.stacked ? (parent.width - transport.spacing) / 2 : (parent.width - playButton.width - transport.spacing * 2) / 2
|
||||
variant: root.zurvan ? "tonal" : "standard"
|
||||
round: !root.zurvan
|
||||
radius: Theme.buttonRadius(width, height, buttonSize, pressed, round)
|
||||
containerColor: MediaAccentService.accentSecondaryContainer
|
||||
contentColor: root.zurvan ? MediaAccentService.onAccentSecondaryContainer : Theme.onSecondaryContainer
|
||||
iconName: "skip_previous"
|
||||
@@ -221,6 +229,7 @@ Card {
|
||||
width: previousButton.width
|
||||
variant: previousButton.variant
|
||||
round: previousButton.round
|
||||
radius: Theme.buttonRadius(width, height, buttonSize, pressed, round)
|
||||
containerColor: previousButton.containerColor
|
||||
contentColor: previousButton.contentColor
|
||||
iconName: "skip_next"
|
||||
|
||||
@@ -662,6 +662,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(root))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: DashMetrics.wallpaperThumbRadius
|
||||
|
||||
ClippingRectangle {
|
||||
@@ -953,6 +955,8 @@ Item {
|
||||
width: DashMetrics.pageJumpWidth
|
||||
height: jumpColumn.height + Theme.spacingM * 2
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.cornerRadiusM
|
||||
visible: false
|
||||
z: DashMetrics.overlayZ + 1
|
||||
|
||||
@@ -11,6 +11,8 @@ Rectangle {
|
||||
|
||||
radius: Theme.cornerRadiusXL
|
||||
color: DashMetrics.cardColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
LayoutMirroring.enabled: false
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ Rectangle {
|
||||
|
||||
radius: graphics && reading.shape ? Theme.fullRadius(width, height) : Theme.cornerRadiusXL
|
||||
color: graphics && widgetId === "uv" ? "transparent" : DashMetrics.cardColor
|
||||
border.width: graphics && widgetId === "uv" ? 0 : Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
Accessible.role: Accessible.StaticText
|
||||
Accessible.name: reading.label + " " + (hasValue ? reading.value : I18n.tr("Not available")) + " " + (reading.detail ?? "")
|
||||
Accessible.description: graphicDescription
|
||||
|
||||
@@ -40,6 +40,8 @@ Rectangle {
|
||||
implicitHeight: DashMetrics.gridRowUnit * 2 + DashMetrics.gridGap
|
||||
radius: Theme.cornerRadiusXL
|
||||
color: DashMetrics.cardColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -914,8 +914,8 @@ Item {
|
||||
clip: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.cardSurface, 0.9)
|
||||
border.color: inputField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3)
|
||||
border.width: inputField.activeFocus ? 2 : 1
|
||||
border.color: inputField.activeFocus ? Theme.primary : Theme.outlineMedium
|
||||
border.width: inputField.activeFocus ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
|
||||
GreeterUserPicker {
|
||||
id: userPicker
|
||||
|
||||
@@ -623,8 +623,8 @@ Item {
|
||||
Layout.preferredHeight: LockMetrics.fieldHeight
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.cardSurface
|
||||
border.width: passwordField.activeFocus ? Math.max(Theme.outlineWidth, Theme.focusRingWidth) : Theme.outlineWidth
|
||||
border.color: passwordField.activeFocus ? Theme.focusRingColor : Theme.outlineVariant
|
||||
border.width: passwordField.activeFocus ? Math.max(Theme.outlineWidth, Theme.focusRingWidth) : Theme.layerOutlineWidth
|
||||
border.color: passwordField.activeFocus ? Theme.focusRingColor : Theme.outlineMedium
|
||||
Accessible.name: I18n.tr("Password")
|
||||
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Item {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.cardSurface, Theme.notepadTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
border.width: Theme.layerOutlineWidth
|
||||
z: 100
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -365,6 +365,8 @@ Column {
|
||||
visible: searchVisible
|
||||
opacity: searchVisible ? 1 : 0
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -486,7 +488,7 @@ Column {
|
||||
height: parent.height - bottomControls.height - Theme.spacingM - (searchVisible ? searchBar.height + Theme.spacingM : 0)
|
||||
color: Theme.withAlpha(Theme.cardSurface, Theme.notepadTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
border.width: Theme.layerOutlineWidth
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
RowLayout {
|
||||
@@ -720,6 +722,8 @@ Column {
|
||||
anchors.right: parent.right
|
||||
height: 36
|
||||
color: Theme.withAlpha(Theme.cardSurface, Theme.notepadTransparency)
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
z: 2
|
||||
|
||||
Row {
|
||||
@@ -930,7 +934,7 @@ Column {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
border.width: Theme.layerOutlineWidth
|
||||
z: 10
|
||||
|
||||
Row {
|
||||
|
||||
@@ -74,6 +74,8 @@ Rectangle {
|
||||
bottomLeftRadius: lastInGroup ? outerRadius : radius
|
||||
bottomRightRadius: bottomLeftRadius
|
||||
color: surfaceColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
clip: true
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
@@ -294,6 +294,7 @@ DankOSD {
|
||||
round: false
|
||||
checkable: true
|
||||
checked: root.playing
|
||||
iconFilled: false
|
||||
iconName: root._displayIcon
|
||||
containerColor: MediaAccentService.accentContainer
|
||||
contentColor: MediaAccentService.onAccentContainer
|
||||
@@ -360,6 +361,7 @@ DankOSD {
|
||||
round: false
|
||||
checkable: true
|
||||
checked: root.playing
|
||||
iconFilled: false
|
||||
backgroundColor: "transparent"
|
||||
iconColor: MediaAccentService.onAccent
|
||||
iconName: root._displayIcon
|
||||
|
||||
@@ -23,6 +23,8 @@ Item {
|
||||
Layout.preferredHeight: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -100,6 +102,8 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -23,6 +23,8 @@ Rectangle {
|
||||
implicitHeight: compact ? cardContent.implicitHeight + Theme.spacingM * 2 : 0
|
||||
radius: Theme.cornerRadiusL
|
||||
color: Theme.foregroundColor(Theme.cardSurface, Theme.isFloatingWindow(card))
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Connections {
|
||||
target: DgopService
|
||||
|
||||
@@ -201,6 +201,8 @@ Popup {
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.windowRadius
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ Item {
|
||||
Layout.preferredHeight: systemInfoColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
ColumnLayout {
|
||||
id: systemInfoColumn
|
||||
@@ -95,6 +97,8 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.nestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -56,29 +56,19 @@ Item {
|
||||
subtitle: I18n.tr("The Island holds this edge on a display this bar covers, so the bar stays hidden there")
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
body: Item {
|
||||
width: parent.width
|
||||
height: positionButtonGroup.height
|
||||
|
||||
DankButtonGroup {
|
||||
id: positionButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: bar.positionChoices.map(pos => bar.positionLabel(pos))
|
||||
currentIndex: {
|
||||
bar.selectedBarId;
|
||||
const config = SettingsData.getBarConfig(bar.selectedBarId);
|
||||
return bar.positionChoices.indexOf(config?.position ?? SettingsData.Position.Top);
|
||||
}
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected || index < 0 || index >= bar.positionChoices.length)
|
||||
return;
|
||||
SettingsData.updateBarConfig(bar.selectedBarId, {
|
||||
position: bar.positionChoices[index]
|
||||
});
|
||||
bar.notifyHorizontalBarChange();
|
||||
}
|
||||
}
|
||||
SettingsLayoutPicker {
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: bar.positionLabel(position),
|
||||
enabled: bar.positionChoices.includes(position)
|
||||
}))
|
||||
selectedKey: String(bar.selectedBarConfig?.position ?? SettingsData.Position.Top)
|
||||
onSelected: key => {
|
||||
SettingsData.updateBarConfig(bar.selectedBarId, {
|
||||
position: Number(key)
|
||||
});
|
||||
bar.notifyHorizontalBarChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ WidgetPickerWindow {
|
||||
radius: Theme.cornerRadius
|
||||
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
|
||||
color: isSelected ? Theme.primarySelected : widgetArea.containsMouse ? Theme.primaryHover : Theme.floatingWindowNestedSurface
|
||||
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.outline, 0.2)
|
||||
border.width: isSelected ? Theme.outlineWidthFocused : Theme.outlineWidth
|
||||
border.color: isSelected ? Theme.primary : Theme.outlineMedium
|
||||
border.width: isSelected ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -164,8 +164,8 @@ DankModal {
|
||||
height: parent.height - 120
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
clip: true
|
||||
|
||||
DankFlickable {
|
||||
|
||||
@@ -142,7 +142,7 @@ Column {
|
||||
tags: ["dock", "layout", "placement", "position", "edge"]
|
||||
|
||||
SettingsLayoutPicker {
|
||||
dockPlacement: true
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: dock.positionLabel(position),
|
||||
|
||||
@@ -97,8 +97,8 @@ Item {
|
||||
height: 56
|
||||
radius: Theme.cornerRadius
|
||||
color: modemMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.floatingWindowNestedSurface
|
||||
border.width: isConnected ? Theme.outlineWidthFocused : 0
|
||||
border.color: Theme.primary
|
||||
border.width: isConnected ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: isConnected ? Theme.primary : Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -232,8 +232,8 @@ Item {
|
||||
height: 56
|
||||
radius: Theme.cornerRadius
|
||||
color: profileMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.floatingWindowNestedSurface
|
||||
border.color: isActive ? Theme.primary : Theme.outlineLight
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.outlineWidth
|
||||
border.color: isActive ? Theme.primary : Theme.outlineMedium
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -85,8 +85,8 @@ Item {
|
||||
height: isExpanded ? 56 + ethExpandedContent.height : 56
|
||||
radius: Theme.cornerRadius
|
||||
color: ethDeviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.floatingWindowNestedSurface
|
||||
border.width: isConnected ? Theme.outlineWidthFocused : 0
|
||||
border.color: Theme.primary
|
||||
border.width: isConnected ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: isConnected ? Theme.primary : Theme.outlineMedium
|
||||
clip: true
|
||||
|
||||
Behavior on height {
|
||||
@@ -381,8 +381,8 @@ Item {
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: wiredMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.floatingWindowNestedSurface
|
||||
border.width: modelData.isActive ? Theme.outlineWidthFocused : 0
|
||||
border.color: Theme.primary
|
||||
border.width: modelData.isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: modelData.isActive ? Theme.primary : Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -231,8 +231,8 @@ Item {
|
||||
height: isExpanded ? 56 + vpnExpandedContent.height : 56
|
||||
radius: Theme.cornerRadius
|
||||
color: vpnRowArea.containsMouse ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.floatingWindowNestedSurface)
|
||||
border.width: isActive ? Theme.outlineWidthFocused : 0
|
||||
border.color: Theme.primary
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: isActive ? Theme.primary : Theme.outlineMedium
|
||||
opacity: DMSNetworkService.isBusy ? 0.6 : 1.0
|
||||
clip: true
|
||||
|
||||
|
||||
@@ -966,8 +966,8 @@ RegistryBrowserWindow {
|
||||
height: Math.round(width * SettingsMetrics.choiceCardPreviewRatio)
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineVariant
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -27,8 +27,8 @@ DankCard {
|
||||
implicitHeight: previewHeight + infoHeight + Theme.spacingS * 2 + Theme.spacingM
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.focusRingColor
|
||||
border.width: selected ? Theme.focusRingWidth : 0
|
||||
border.color: selected ? Theme.focusRingColor : Theme.outlineMedium
|
||||
border.width: selected ? Theme.focusRingWidth : Theme.layerOutlineWidth
|
||||
pad: 0
|
||||
clickable: true
|
||||
Accessible.name: plugin.name || ""
|
||||
|
||||
@@ -21,8 +21,8 @@ StyledRect {
|
||||
height: visible ? innerColumn.implicitHeight + Theme.spacingL * 2 : 0
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: Theme.outlineVariant
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
clip: true
|
||||
|
||||
visible: false
|
||||
|
||||
@@ -195,6 +195,8 @@ Item {
|
||||
height: 90
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
@@ -523,8 +525,8 @@ Item {
|
||||
height: themeGrid.cardHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.color: isActive ? Theme.primary : Theme.outline
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.outlineWidth
|
||||
border.color: isActive ? Theme.primary : Theme.outlineMedium
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
scale: isActive ? 1.03 : 1
|
||||
|
||||
Behavior on scale {
|
||||
|
||||
@@ -178,8 +178,9 @@ Column {
|
||||
tags: ["foreground", "layers", "outline", "border", "cards", "pills", "widgets", "notifications", "control center"]
|
||||
settingKey: "blurLayerOutlineOpacity"
|
||||
text: I18n.tr("Layer outline opacity")
|
||||
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0.12) * 100)
|
||||
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0) * 100)
|
||||
minimum: 0
|
||||
minimumLabel: I18n.tr("Off")
|
||||
maximum: 40
|
||||
onSliderValueChanged: newValue => SettingsData.set("blurLayerOutlineOpacity", newValue / 100)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,8 @@ Item {
|
||||
bottomLeftRadius: last ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRightRadius: last ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
color: root.highlighted ? Theme.blend(root.slotColor, Theme.primary, SettingsMetrics.highlightBlend) : root.slotColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Behavior on color {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Modules.DankBar
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
GridLayout {
|
||||
id: root
|
||||
|
||||
property bool dockPlacement: false
|
||||
property bool edgePlacement: false
|
||||
property bool widgetStyle: false
|
||||
property var choices: barModes
|
||||
property string selectedKey: activeBarMode
|
||||
signal selected(string key)
|
||||
onSelected: key => {
|
||||
if (!dockPlacement && !widgetStyle)
|
||||
if (!edgePlacement && !widgetStyle)
|
||||
applyBarMode(key);
|
||||
}
|
||||
|
||||
@@ -37,6 +38,8 @@ Row {
|
||||
readonly property real previewAspect: 0.62
|
||||
readonly property real previewStripRatio: 0.55
|
||||
readonly property real previewIslandRatio: 0.42
|
||||
readonly property real previewWidth: Math.round(Theme.iconSize * previewWidthRatio)
|
||||
readonly property real minimumCardWidth: previewWidth + Theme.spacingL * 2
|
||||
readonly property var targetConfig: {
|
||||
SettingsData.barConfigs;
|
||||
SettingsUiState.selectedBarId;
|
||||
@@ -70,7 +73,13 @@ Row {
|
||||
}
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.spacingS
|
||||
columns: {
|
||||
const count = Math.max(1, root.choices.length);
|
||||
const availableColumns = Math.max(1, Math.floor((width + columnSpacing) / (minimumCardWidth + columnSpacing)));
|
||||
return Math.ceil(count / Math.ceil(count / availableColumns));
|
||||
}
|
||||
columnSpacing: Theme.spacingS
|
||||
rowSpacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.choices
|
||||
@@ -83,11 +92,14 @@ Row {
|
||||
|
||||
readonly property bool isActive: root.selectedKey === modelData.key
|
||||
|
||||
width: (root.width - root.spacing * (root.choices.length - 1)) / Math.max(1, root.choices.length)
|
||||
height: Math.round(Theme.fontSizeMedium * root.cardHeightRatio)
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: 0
|
||||
implicitWidth: root.minimumCardWidth
|
||||
implicitHeight: Math.max(Math.round(Theme.fontSizeMedium * root.cardHeightRatio), cardContent.implicitHeight + Theme.spacingM * 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.outlineWidth
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: isActive ? Theme.primary : Theme.outlineMedium
|
||||
|
||||
activeFocusOnTab: true
|
||||
@@ -108,7 +120,10 @@ Row {
|
||||
}
|
||||
|
||||
Column {
|
||||
id: cardContent
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: Math.max(0, parent.width - Theme.spacingM * 2)
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
@@ -116,7 +131,7 @@ Row {
|
||||
readonly property real edgePad: Math.max(2, Math.round(width * 0.045))
|
||||
readonly property real stripSize: Math.round(width * 0.11)
|
||||
|
||||
width: Math.round(Theme.iconSize * root.previewWidthRatio)
|
||||
width: Math.min(root.previewWidth, cardContent.width)
|
||||
height: Math.round(width * root.previewAspect)
|
||||
radius: Theme.spacingXS
|
||||
color: Theme.chipSurface
|
||||
@@ -148,7 +163,7 @@ Row {
|
||||
Rectangle {
|
||||
readonly property int edge: Number(modeCard.modelData.key)
|
||||
readonly property bool vertical: edge === SettingsData.Position.Left || edge === SettingsData.Position.Right
|
||||
visible: root.dockPlacement
|
||||
visible: root.edgePlacement
|
||||
x: edge === SettingsData.Position.Right ? parent.width - width - screenPreview.edgePad : screenPreview.edgePad
|
||||
y: edge === SettingsData.Position.Bottom ? parent.height - height - screenPreview.edgePad : screenPreview.edgePad
|
||||
width: vertical ? screenPreview.stripSize : parent.width - screenPreview.edgePad * 2
|
||||
@@ -158,7 +173,7 @@ Row {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !root.widgetStyle && !root.dockPlacement && modeCard.modelData.key === "standard"
|
||||
visible: !root.widgetStyle && !root.edgePlacement && modeCard.modelData.key === "standard"
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -202,11 +217,13 @@ Row {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: modeCard.modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: modeCard.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ T.Control {
|
||||
anchors.fill: parent
|
||||
visible: root.paintBackground
|
||||
color: root.isHighlighted ? Theme.blend(root.rowColor, Theme.primary, SettingsMetrics.highlightBlend) : root.rowColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
topLeftRadius: root.topRadius
|
||||
topRightRadius: root.topRadius
|
||||
bottomLeftRadius: root.bottomRadius
|
||||
|
||||
@@ -46,6 +46,8 @@ Rectangle {
|
||||
bottomLeftRadius: bottomRadius
|
||||
bottomRightRadius: bottomRadius
|
||||
color: active ? SettingsMetrics.selectedRowColor : SettingsMetrics.rowColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Behavior on color {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
|
||||
@@ -46,6 +46,8 @@ Item {
|
||||
Layer {
|
||||
label: I18n.tr("Cards")
|
||||
color: Theme.cardSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
radius: Theme.cornerRadiusM
|
||||
|
||||
Layer {
|
||||
|
||||
@@ -37,7 +37,7 @@ Flow {
|
||||
height: root.compact ? root.compactTileHeight : swatch.height + label.implicitHeight + Theme.spacingS * 3
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.outlineWidth
|
||||
border.width: isActive ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
|
||||
border.color: isActive ? Theme.primary : Theme.outlineMedium
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
@@ -37,6 +37,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
radius: Theme.groupedListOuterRadius
|
||||
color: SettingsMetrics.rowColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: root.standalone
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
.pragma library
|
||||
|
||||
function create() {
|
||||
return {
|
||||
paths: Object.create(null),
|
||||
scores: Object.create(null),
|
||||
directories: Object.create(null)
|
||||
};
|
||||
}
|
||||
|
||||
function add(index, path, searchDirs, looseDirs) {
|
||||
const separator = path.lastIndexOf("/") + 1;
|
||||
const fileName = path.substring(separator);
|
||||
const name = fileName.replace(/\.(svg|png|xpm)$/, "");
|
||||
if (!/^[\w.+-]+$/.test(name))
|
||||
return;
|
||||
const directory = path.substring(0, separator);
|
||||
let directoryScore = index.directories[directory];
|
||||
if (directoryScore === undefined) {
|
||||
directoryScore = score(directory, searchDirs, looseDirs);
|
||||
index.directories[directory] = directoryScore;
|
||||
}
|
||||
const candidateScore = directoryScore + (fileName.endsWith(".svg") ? 100000 : 0);
|
||||
const currentScore = index.scores[name];
|
||||
if (currentScore !== undefined && candidateScore <= currentScore)
|
||||
return;
|
||||
index.paths[name] = path;
|
||||
index.scores[name] = candidateScore;
|
||||
}
|
||||
|
||||
function chainIndex(path, searchDirs, looseDirs) {
|
||||
for (let i = 0; i < searchDirs.length; i++) {
|
||||
if (path.startsWith(searchDirs[i] + "/"))
|
||||
return i;
|
||||
}
|
||||
for (let i = 0; i < looseDirs.length; i++) {
|
||||
if (path.startsWith(looseDirs[i] + "/"))
|
||||
return searchDirs.length + i;
|
||||
}
|
||||
return searchDirs.length + looseDirs.length;
|
||||
}
|
||||
|
||||
function score(path, searchDirs, looseDirs) {
|
||||
let s = 0;
|
||||
if (path.includes("/apps/"))
|
||||
s += 3000000000;
|
||||
else if (path.includes("/categories/"))
|
||||
s += 1000000000;
|
||||
else if (path.includes("/places/") || path.includes("/devices/") || path.includes("/mimetypes/") || path.includes("/status/") || path.includes("/actions/"))
|
||||
s += 100000000;
|
||||
|
||||
s += Math.min(99, Math.max(0, searchDirs.length + looseDirs.length - chainIndex(path, searchDirs, looseDirs))) * 1000000;
|
||||
|
||||
if (path.endsWith(".svg"))
|
||||
s += 100000;
|
||||
|
||||
if (path.includes("/scalable/")) {
|
||||
s += 1000;
|
||||
} else {
|
||||
const m = path.match(/\/(\d+)(?:x\d+)?(?:@\d+x)?\//);
|
||||
if (m)
|
||||
s += Math.min(parseInt(m[1]), 999);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -4,9 +4,11 @@ pragma ComponentBehavior: Bound
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../Common/GSettings.js" as GSettings // ! This import is actually used even though qmlls claims it isnt, do not delete
|
||||
import "../Common/GSettings.js" as GSettings // qmllint disable unused-imports
|
||||
import "IconThemeIndex.js" as IconIndex
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -15,23 +17,18 @@ Singleton {
|
||||
readonly property string settingsTheme: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return "";
|
||||
const t = SettingsData.resolveIconTheme();
|
||||
return (!t || t === "System Default") ? "" : t;
|
||||
const theme = SettingsData.resolveIconTheme();
|
||||
return (!theme || theme === "System Default") ? "" : theme;
|
||||
}
|
||||
property string systemProbedTheme: ""
|
||||
readonly property string managedTheme: settingsTheme || systemProbedTheme
|
||||
property bool ready: false
|
||||
property var _iconPaths: Object.create(null)
|
||||
property bool _initialized: false
|
||||
property bool _probing: false
|
||||
property bool _building: false
|
||||
property bool _rebuildPending: false
|
||||
|
||||
property var _searchDirs: []
|
||||
property string _dirsForTheme: ""
|
||||
property var _cache: ({})
|
||||
property var _looseIconPaths: ({})
|
||||
property int _cacheGeneration: 0
|
||||
property int _rebuildGeneration: 0
|
||||
property int _looseIndexGeneration: 0
|
||||
property int revision: 0
|
||||
property bool _bumpPending: false
|
||||
|
||||
// XDG icon spec order: user dirs win over system ones, so overrides in ~/.local/share/icons beat installed themes.
|
||||
readonly property var _userIconRoots: {
|
||||
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
|
||||
const home = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
|
||||
@@ -59,101 +56,63 @@ Singleton {
|
||||
return bases;
|
||||
}
|
||||
|
||||
// Loose icon files (AppImage managers, legacy /usr/share/pixmaps installs) live outside any theme.
|
||||
readonly property var _looseDirs: {
|
||||
const dirs = _userIconRoots.concat(_dataDirs.map(d => d + "/pixmaps"));
|
||||
return dirs.filter((dir, index) => dirs.indexOf(dir) === index);
|
||||
}
|
||||
|
||||
onManagedThemeChanged: _rebuild()
|
||||
onSettingsThemeChanged: {
|
||||
if (_initialized && !settingsTheme)
|
||||
_probeSystemTheme();
|
||||
}
|
||||
onManagedThemeChanged: Qt.callLater(_rebuild)
|
||||
|
||||
Component.onCompleted: {
|
||||
_initialized = true;
|
||||
Paths.iconResolver = name => resolve(name);
|
||||
_probeSystemTheme();
|
||||
_rebuild();
|
||||
Qt.callLater(_rebuild);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
|
||||
function onApplicationsChanged() {
|
||||
root._indexLooseIcons(() => root._invalidateMisses());
|
||||
if (!root.ready)
|
||||
return;
|
||||
Qt.callLater(root._rebuild);
|
||||
}
|
||||
}
|
||||
|
||||
// "System Default" leaves Qt's lookup, which sees only hicolor unless a Qt platform theme or QS_ICON_THEME is configured.
|
||||
function _probeSystemTheme() {
|
||||
if (Quickshell.env("QS_ICON_THEME"))
|
||||
if (Quickshell.env("QS_ICON_THEME") || _probing)
|
||||
return;
|
||||
_probing = true;
|
||||
const script = `v=$(sed -n 's/^gtk-icon-theme-name *= *//p' "\${XDG_CONFIG_HOME:-$HOME/.config}/gtk-3.0/settings.ini" 2>/dev/null | head -1)
|
||||
[ -z "$v" ] && v=$(${GSettings.getCmd("org.gnome.desktop.interface", "icon-theme")})
|
||||
printf '%s' "$v" | tr -d "'\\""`;
|
||||
|
||||
Proc.runCommand("iconThemeProbe", ["sh", "-c", script], (out, code) => {
|
||||
const theme = (out || "").trim();
|
||||
if (!theme)
|
||||
return;
|
||||
root.systemProbedTheme = theme;
|
||||
});
|
||||
}
|
||||
|
||||
function _bumpRevision() {
|
||||
if (_bumpPending)
|
||||
return;
|
||||
_bumpPending = true;
|
||||
Qt.callLater(() => {
|
||||
_bumpPending = false;
|
||||
revision++;
|
||||
});
|
||||
}
|
||||
|
||||
function _invalidateMisses() {
|
||||
const c = {};
|
||||
let changed = false;
|
||||
for (const name in _cache) {
|
||||
if (_cache[name])
|
||||
c[name] = _cache[name];
|
||||
else
|
||||
changed = true;
|
||||
}
|
||||
if (!changed)
|
||||
return;
|
||||
_cacheGeneration++;
|
||||
_cache = c;
|
||||
_bumpRevision();
|
||||
}
|
||||
|
||||
function _indexLooseIcons(callback) {
|
||||
const generation = ++_looseIndexGeneration;
|
||||
const args = ["find", "-L"].concat(_looseDirs, ["-maxdepth", "1", "(", "-name", "*.svg", "-o", "-name", "*.png", "-o", "-name", "*.xpm", ")"]);
|
||||
Proc.runCommand("iconLooseIndex", args, (out, code) => {
|
||||
if (root._looseIndexGeneration !== generation)
|
||||
return;
|
||||
const index = {};
|
||||
const paths = (out || "").trim().split("\n").filter(s => s);
|
||||
for (const path of paths) {
|
||||
const fileName = path.substring(path.lastIndexOf("/") + 1);
|
||||
const name = fileName.replace(/\.(svg|png|xpm)$/, "");
|
||||
const current = index[name];
|
||||
index[name] = current ? root._pickBest([current, path]) : path;
|
||||
}
|
||||
root._looseIconPaths = index;
|
||||
if (callback)
|
||||
callback();
|
||||
Proc.runCommand(null, ["sh", "-c", script], (out, code) => {
|
||||
root.systemProbedTheme = (out || "").trim();
|
||||
root._probing = false;
|
||||
Qt.callLater(root._rebuild);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function _rebuild() {
|
||||
const rebuildGeneration = ++_rebuildGeneration;
|
||||
_cacheGeneration++;
|
||||
_cache = ({});
|
||||
_dirsForTheme = "";
|
||||
_indexLooseIcons(() => root._invalidateMisses());
|
||||
if (!managedTheme) {
|
||||
_searchDirs = [];
|
||||
_bumpRevision();
|
||||
if (!_initialized || _probing)
|
||||
return;
|
||||
if (_building) {
|
||||
_rebuildPending = true;
|
||||
return;
|
||||
}
|
||||
_building = true;
|
||||
_rebuildPending = false;
|
||||
const theme = managedTheme;
|
||||
if (!theme) {
|
||||
_publishIndex(theme, Object.create(null));
|
||||
return;
|
||||
}
|
||||
const script = `theme=$1
|
||||
shift
|
||||
find_index() { target=$1; shift; for b; do [ -f "$b/$target/index.theme" ] && { echo "$b/$target/index.theme"; return 0; }; done; return 1; }
|
||||
@@ -170,105 +129,101 @@ done
|
||||
case " $visited " in *" hicolor "*) ;; *) order="$order hicolor";; esac
|
||||
for t in $order; do for b; do d="$b/$t"; [ -d "$d" ] && echo "$d"; done; done`;
|
||||
|
||||
Proc.runCommand("iconChain:" + theme, ["sh", "-c", script, "icon-chain", theme].concat(_baseDirs), (out, code) => {
|
||||
if (root.managedTheme !== theme || root._rebuildGeneration !== rebuildGeneration)
|
||||
return;
|
||||
root._searchDirs = (out || "").trim().split("\n").filter(s => s);
|
||||
root._dirsForTheme = theme;
|
||||
root._cacheGeneration++;
|
||||
root._cache = ({});
|
||||
root._bumpRevision();
|
||||
});
|
||||
}
|
||||
|
||||
function resolve(name) {
|
||||
const _dep = revision;
|
||||
if (!managedTheme || !name)
|
||||
return "";
|
||||
if (name.startsWith("/") || name.startsWith("file://") || name.startsWith("image://") || name.startsWith("~"))
|
||||
return "";
|
||||
if (!/^[\w.+-]+$/.test(name))
|
||||
return "";
|
||||
if (_dirsForTheme !== managedTheme)
|
||||
return "";
|
||||
if (name in _cache)
|
||||
return _cache[name] || "";
|
||||
_cache[name] = null;
|
||||
_resolveAsync(name);
|
||||
return "";
|
||||
}
|
||||
|
||||
function _resolveAsync(name) {
|
||||
const generation = _cacheGeneration;
|
||||
|
||||
function finish(paths) {
|
||||
if (root._cacheGeneration !== generation)
|
||||
return;
|
||||
const best = paths.length > 0 ? root._pickBest(paths) : root._looseIconPaths[name] || "";
|
||||
const c = root._cache;
|
||||
c[name] = best ? Paths.toFileUrl(best) : "";
|
||||
root._cache = c;
|
||||
root._bumpRevision();
|
||||
}
|
||||
|
||||
if (_searchDirs.length === 0) {
|
||||
finish([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const args = ["find", "-L"].concat(_searchDirs, ["(", "-name", name + ".svg", "-o", "-name", name + ".png", ")"]);
|
||||
Proc.runCommand("iconResolveTheme:" + name, args, (out, code) => {
|
||||
const paths = (out || "").trim().split("\n").filter(s => s);
|
||||
finish(paths);
|
||||
Proc.runCommand(null, ["sh", "-c", script, "icon-chain", theme].concat(_baseDirs), (out, code) => {
|
||||
const dirs = (out || "").trim().split("\n").filter(dir => dir);
|
||||
root._indexTheme(theme, dirs);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function _pickBest(paths) {
|
||||
let best = "";
|
||||
let bestScore = -1;
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const s = _score(paths[i]);
|
||||
if (s > bestScore) {
|
||||
bestScore = s;
|
||||
best = paths[i];
|
||||
function _indexTheme(theme, dirs) {
|
||||
indexJob.createObject(root, {
|
||||
theme: theme,
|
||||
searchDirs: dirs,
|
||||
looseDirs: _looseDirs
|
||||
});
|
||||
}
|
||||
|
||||
function _publishIndex(theme, paths) {
|
||||
_building = false;
|
||||
if (managedTheme === theme && !_probing) {
|
||||
_iconPaths = paths;
|
||||
ready = true;
|
||||
}
|
||||
if (_rebuildPending)
|
||||
Qt.callLater(_rebuild);
|
||||
}
|
||||
|
||||
Component {
|
||||
id: indexJob
|
||||
|
||||
Scope {
|
||||
id: job
|
||||
|
||||
required property string theme
|
||||
required property var searchDirs
|
||||
required property var looseDirs
|
||||
property var themeIndex: IconIndex.create()
|
||||
property var looseIndex: IconIndex.create()
|
||||
property int remaining: searchDirs.length ? 2 : 1
|
||||
property bool finished: false
|
||||
|
||||
function complete() {
|
||||
if (--remaining > 0)
|
||||
return;
|
||||
publish();
|
||||
}
|
||||
|
||||
function publish() {
|
||||
if (finished)
|
||||
return;
|
||||
finished = true;
|
||||
for (const name in looseIndex.paths) {
|
||||
if (!themeIndex.paths[name])
|
||||
themeIndex.paths[name] = looseIndex.paths[name];
|
||||
}
|
||||
root._publishIndex(theme, themeIndex.paths);
|
||||
destroy();
|
||||
}
|
||||
|
||||
Process {
|
||||
command: ["find", "-L"].concat(job.searchDirs, ["(", "-name", "*.svg", "-o", "-name", "*.png", ")"])
|
||||
running: job.searchDirs.length > 0
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if (!job.finished)
|
||||
IconIndex.add(job.themeIndex, data, job.searchDirs, job.looseDirs);
|
||||
}
|
||||
}
|
||||
onExited: job.complete()
|
||||
}
|
||||
|
||||
Process {
|
||||
command: ["find", "-L"].concat(job.looseDirs, ["-maxdepth", "1", "(", "-name", "*.svg", "-o", "-name", "*.png", "-o", "-name", "*.xpm", ")"])
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if (!job.finished)
|
||||
IconIndex.add(job.looseIndex, data, job.searchDirs, job.looseDirs);
|
||||
}
|
||||
}
|
||||
onExited: job.complete()
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10000
|
||||
running: true
|
||||
onTriggered: {
|
||||
root.log.warn("Icon theme index timed out:", job.theme);
|
||||
job.publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function _chainIndex(path) {
|
||||
for (let i = 0; i < _searchDirs.length; i++) {
|
||||
if (path.startsWith(_searchDirs[i] + "/"))
|
||||
return i;
|
||||
}
|
||||
for (let i = 0; i < _looseDirs.length; i++) {
|
||||
if (path.startsWith(_looseDirs[i] + "/"))
|
||||
return _searchDirs.length + i;
|
||||
}
|
||||
return _searchDirs.length + _looseDirs.length;
|
||||
}
|
||||
|
||||
function _score(path) {
|
||||
let s = 0;
|
||||
if (path.includes("/apps/"))
|
||||
s += 3000000000;
|
||||
else if (path.includes("/categories/"))
|
||||
s += 1000000000;
|
||||
else if (path.includes("/places/") || path.includes("/devices/") || path.includes("/mimetypes/") || path.includes("/status/") || path.includes("/actions/"))
|
||||
s += 100000000;
|
||||
|
||||
s += Math.min(99, Math.max(0, _searchDirs.length + _looseDirs.length - _chainIndex(path))) * 1000000;
|
||||
|
||||
if (path.endsWith(".svg"))
|
||||
s += 100000;
|
||||
|
||||
if (path.includes("/scalable/")) {
|
||||
s += 1000;
|
||||
} else {
|
||||
const m = path.match(/\/(\d+)(?:x\d+)?(?:@\d+x)?\//);
|
||||
if (m)
|
||||
s += Math.min(parseInt(m[1]), 999);
|
||||
}
|
||||
return s;
|
||||
function resolve(name) {
|
||||
if (!name || !/^[\w.+-]+$/.test(name))
|
||||
return "";
|
||||
const path = _iconPaths[name];
|
||||
return path ? Paths.toFileUrl(path) : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ ShellRoot {
|
||||
|
||||
Loader {
|
||||
id: shellCoreLoader
|
||||
active: !entrypoint.runGreeter
|
||||
active: !entrypoint.runGreeter && IconThemeService.ready
|
||||
asynchronous: true
|
||||
source: "ShellCore.qml"
|
||||
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
|
||||
@@ -73,7 +73,7 @@ ShellRoot {
|
||||
|
||||
Loader {
|
||||
id: dmsGreeterLoader
|
||||
active: entrypoint.runGreeter
|
||||
active: entrypoint.runGreeter && IconThemeService.ready
|
||||
asynchronous: false
|
||||
source: "DMSGreeter.qml"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import vm from "node:vm";
|
||||
import test from "node:test";
|
||||
|
||||
const index = vm.createContext({});
|
||||
vm.runInContext(readFileSync(new URL("../Services/IconThemeIndex.js", import.meta.url), "utf8").replace(/^\.pragma.*$/m, ""), index);
|
||||
const searchDirs = ["/user/theme", "/system/theme", "/system/inherited", "/system/hicolor"];
|
||||
const looseDirs = ["/user/icons", "/system/pixmaps"];
|
||||
|
||||
function build(paths) {
|
||||
const result = index.create();
|
||||
for (const path of paths)
|
||||
index.add(result, path, searchDirs, looseDirs);
|
||||
return result.paths;
|
||||
}
|
||||
|
||||
test("index keeps the existing category, root, format, size and tie priorities", () => {
|
||||
const cases = [
|
||||
["/user/theme/actions/icon.svg", "/system/inherited/apps/icon.png"],
|
||||
["/system/theme/apps/scalable/icon.svg", "/user/theme/apps/16/icon.png"],
|
||||
["/user/theme/apps/512/icon.png", "/user/theme/apps/16/icon.svg"],
|
||||
["/user/theme/apps/512/icon.svg", "/user/theme/apps/scalable/icon.svg"],
|
||||
["/user/theme/apps/16x16/icon.png", "/user/theme/apps/48x48@2x/icon.png"],
|
||||
["/system/pixmaps/icon.svg", "/user/icons/icon.png"]
|
||||
];
|
||||
for (const [loser, winner] of cases) {
|
||||
assert.equal(build([loser, winner]).icon, winner);
|
||||
assert.equal(build([winner, loser]).icon, winner);
|
||||
}
|
||||
const ties = ["/user/theme/apps/a/icon.svg", "/user/theme/apps/b/icon.svg"];
|
||||
assert.equal(build(ties).icon, ties[0]);
|
||||
});
|
||||
|
||||
test("index bounds retained paths by resolvable names and handles object property names", () => {
|
||||
const paths = ["/user/theme/apps/icon.svg", "/user/theme/apps/16/icon.png", "/system/pixmaps/loose.xpm", "/user/theme/apps/__proto__.svg", "/user/theme/apps/constructor.png", "/user/theme/apps/not a name.svg"];
|
||||
const result = build(paths);
|
||||
assert.equal(Object.keys(result).length, 4);
|
||||
assert.equal(result.__proto__, paths[3]);
|
||||
assert.equal(result.constructor, paths[4]);
|
||||
assert.equal(result.loose, paths[2]);
|
||||
});
|
||||
|
||||
test("first lookup uses the completed index without spawning or retaining misses", () => {
|
||||
const source = readFileSync(process.env.ICON_SERVICE_SOURCE || new URL("../Services/IconThemeService.qml", import.meta.url), "utf8");
|
||||
const resolve = source.match(/^ function resolve\(name\) \{[\s\S]*?^ \}/m)[0];
|
||||
let spawns = 0;
|
||||
const context = vm.createContext({
|
||||
_iconPaths: build(["/user/theme/apps/icon.svg"]),
|
||||
Paths: { toFileUrl: path => "file://" + path },
|
||||
revision: 0, managedTheme: "theme", _dirsForTheme: "theme", _cache: {},
|
||||
_resolveAsync: () => spawns++
|
||||
});
|
||||
vm.runInContext(resolve, context);
|
||||
assert.equal(context.resolve("icon"), "file:///user/theme/apps/icon.svg");
|
||||
for (let i = 0; i < 100; i++)
|
||||
assert.equal(context.resolve("missing-" + i), "");
|
||||
assert.equal(spawns, 0);
|
||||
assert.equal(Object.keys(context._iconPaths).length, 1);
|
||||
});
|
||||
@@ -81,6 +81,12 @@ def format_js(qmlformat, file):
|
||||
return result.stdout
|
||||
|
||||
|
||||
def truncated(original, new_text):
|
||||
"""qmlls and qmlformat can succeed and still hand back an empty or gutted
|
||||
document; writing that back destroys the file and stages the wreckage."""
|
||||
return not new_text.strip() or len(new_text) < len(original) // 2
|
||||
|
||||
|
||||
def find_qmllint():
|
||||
for candidate in QMLLINT_CANDIDATES:
|
||||
path = candidate if "/" in candidate and Path(candidate).is_file() else shutil.which(candidate)
|
||||
@@ -276,6 +282,10 @@ def format_js_files(qmlformat, root, files):
|
||||
skipped += 1
|
||||
print("skipped (qmlformat rejected the file)")
|
||||
continue
|
||||
if truncated(original, new_text):
|
||||
skipped += 1
|
||||
print("skipped (qmlformat returned a truncated document)")
|
||||
continue
|
||||
if new_text == original:
|
||||
print("unchanged")
|
||||
continue
|
||||
@@ -377,6 +387,10 @@ def main():
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})
|
||||
|
||||
new_text = apply_edits(original, edits or [])
|
||||
if truncated(original, new_text):
|
||||
skipped += 1
|
||||
print("skipped (qmlls returned a truncated document)")
|
||||
continue
|
||||
if new_text == original:
|
||||
print("unchanged")
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user