Merge settings window to main #23
+81
-53
@@ -9,7 +9,11 @@ import qs.Config
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values).sort((a, b) => a.name.localeCompare(b.name))
|
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values).filter((app, index, self) => index === self.findIndex(t => (t.id === app.id)))
|
||||||
|
readonly property var preppedIcons: list.map(a => ({
|
||||||
|
name: Fuzzy.prepare(`${a.icon} `),
|
||||||
|
entry: a
|
||||||
|
}))
|
||||||
readonly property var preppedNames: list.map(a => ({
|
readonly property var preppedNames: list.map(a => ({
|
||||||
name: Fuzzy.prepare(`${a.name} `),
|
name: Fuzzy.prepare(`${a.name} `),
|
||||||
entry: a
|
entry: a
|
||||||
@@ -32,8 +36,8 @@ Singleton {
|
|||||||
"replace": "system-lock-screen"
|
"replace": "system-lock-screen"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
property real scoreThreshold: 0.2
|
readonly property real scoreGapThreshold: 0.1
|
||||||
property bool sloppySearch: Config.options?.search.sloppy ?? false
|
readonly property real scoreThreshold: 0.6
|
||||||
property var substitutions: ({
|
property var substitutions: ({
|
||||||
"code-url-handler": "visual-studio-code",
|
"code-url-handler": "visual-studio-code",
|
||||||
"Code": "visual-studio-code",
|
"Code": "visual-studio-code",
|
||||||
@@ -41,46 +45,65 @@ Singleton {
|
|||||||
"pavucontrol-qt": "pavucontrol",
|
"pavucontrol-qt": "pavucontrol",
|
||||||
"wps": "wps-office2019-kprometheus",
|
"wps": "wps-office2019-kprometheus",
|
||||||
"wpsoffice": "wps-office2019-kprometheus",
|
"wpsoffice": "wps-office2019-kprometheus",
|
||||||
"footclient": "foot",
|
"footclient": "foot"
|
||||||
"zen": "zen-browser"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
signal reload
|
function bestFuzzyEntry(search: string, preppedList: list<var>, key: string): var {
|
||||||
|
const results = Fuzzy.go(search, preppedList, {
|
||||||
function computeScore(...args) {
|
key: key,
|
||||||
return Levendist.computeScore(...args);
|
threshold: root.scoreThreshold,
|
||||||
}
|
limit: 2
|
||||||
|
|
||||||
function computeTextMatchScore(...args) {
|
|
||||||
return Levendist.computeTextMatchScore(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work
|
|
||||||
if (root.sloppySearch) {
|
|
||||||
const results = list.map(obj => ({
|
|
||||||
entry: obj,
|
|
||||||
score: computeScore(obj.name.toLowerCase(), search.toLowerCase())
|
|
||||||
})).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score);
|
|
||||||
return results.map(item => item.entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Fuzzy.go(search, preppedNames, {
|
|
||||||
all: true,
|
|
||||||
key: "name"
|
|
||||||
}).map(r => {
|
|
||||||
return r.obj.entry;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!results || results.length === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const best = results[0];
|
||||||
|
const second = results.length > 1 ? results[1] : null;
|
||||||
|
|
||||||
|
if (second && (best.score - second.score) < root.scoreGapThreshold)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return best.obj.entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fuzzyQuery(search: string, preppedList: list<var>): var {
|
||||||
|
const entry = bestFuzzyEntry(search, preppedList, "name");
|
||||||
|
return entry ? [entry] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKebabNormalizedAppName(str: string): string {
|
||||||
|
return str.toLowerCase().replace(/\s+/g, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReverseDomainNameAppName(str: string): string {
|
||||||
|
return str.split('.').slice(-1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUndescoreToKebabAppName(str: string): string {
|
||||||
|
return str.toLowerCase().replace(/_/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
function guessIcon(str) {
|
function guessIcon(str) {
|
||||||
if (!str || str.length == 0)
|
if (!str || str.length == 0)
|
||||||
return "image-missing";
|
return "image-missing";
|
||||||
|
|
||||||
// Normal substitutions
|
if (iconExists(str))
|
||||||
|
return str;
|
||||||
|
|
||||||
|
const entry = DesktopEntries.byId(str);
|
||||||
|
if (entry)
|
||||||
|
return entry.icon;
|
||||||
|
|
||||||
|
const heuristicEntry = DesktopEntries.heuristicLookup(str);
|
||||||
|
if (heuristicEntry)
|
||||||
|
return heuristicEntry.icon;
|
||||||
|
|
||||||
if (substitutions[str])
|
if (substitutions[str])
|
||||||
return substitutions[str];
|
return substitutions[str];
|
||||||
|
if (substitutions[str.toLowerCase()])
|
||||||
|
return substitutions[str.toLowerCase()];
|
||||||
|
|
||||||
// Regex substitutions
|
|
||||||
for (let i = 0; i < regexSubstitutions.length; i++) {
|
for (let i = 0; i < regexSubstitutions.length; i++) {
|
||||||
const substitution = regexSubstitutions[i];
|
const substitution = regexSubstitutions[i];
|
||||||
const replacedName = str.replace(substitution.regex, substitution.replace);
|
const replacedName = str.replace(substitution.regex, substitution.replace);
|
||||||
@@ -88,30 +111,35 @@ Singleton {
|
|||||||
return replacedName;
|
return replacedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it gets detected normally, no need to guess
|
const lowercased = str.toLowerCase();
|
||||||
if (iconExists(str))
|
if (iconExists(lowercased))
|
||||||
return str;
|
return lowercased;
|
||||||
|
|
||||||
let guessStr = str;
|
const reverseDomainNameAppName = getReverseDomainNameAppName(str);
|
||||||
// Guess: Take only app name of reverse domain name notation
|
if (iconExists(reverseDomainNameAppName))
|
||||||
guessStr = str.split('.').slice(-1)[0].toLowerCase();
|
return reverseDomainNameAppName;
|
||||||
if (iconExists(guessStr))
|
|
||||||
return guessStr;
|
|
||||||
// Guess: normalize to kebab case
|
|
||||||
guessStr = str.toLowerCase().replace(/\s+/g, "-");
|
|
||||||
if (iconExists(guessStr))
|
|
||||||
return guessStr;
|
|
||||||
// Guess: First fuzze desktop entry match
|
|
||||||
const searchResults = root.fuzzyQuery(str);
|
|
||||||
if (searchResults.length > 0) {
|
|
||||||
const firstEntry = searchResults[0];
|
|
||||||
guessStr = firstEntry.icon;
|
|
||||||
if (iconExists(guessStr))
|
|
||||||
return guessStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give up
|
const lowercasedDomainNameAppName = reverseDomainNameAppName.toLowerCase();
|
||||||
return str;
|
if (iconExists(lowercasedDomainNameAppName))
|
||||||
|
return lowercasedDomainNameAppName;
|
||||||
|
|
||||||
|
const kebabNormalizedGuess = getKebabNormalizedAppName(str);
|
||||||
|
if (iconExists(kebabNormalizedGuess))
|
||||||
|
return kebabNormalizedGuess;
|
||||||
|
|
||||||
|
const undescoreToKebabGuess = getUndescoreToKebabAppName(str);
|
||||||
|
if (iconExists(undescoreToKebabGuess))
|
||||||
|
return undescoreToKebabGuess;
|
||||||
|
|
||||||
|
const iconSearchResult = fuzzyQuery(str, preppedIcons);
|
||||||
|
if (iconSearchResult && iconExists(iconSearchResult.icon))
|
||||||
|
return iconSearchResult.icon;
|
||||||
|
|
||||||
|
const nameSearchResult = root.fuzzyQuery(str, preppedNames);
|
||||||
|
if (nameSearchResult && iconExists(nameSearchResult.icon))
|
||||||
|
return nameSearchResult.icon;
|
||||||
|
|
||||||
|
return "application-x-executable";
|
||||||
}
|
}
|
||||||
|
|
||||||
function iconExists(iconName) {
|
function iconExists(iconName) {
|
||||||
|
|||||||
+19
-18
@@ -8,7 +8,7 @@ import qs.Config
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property list<var> apps: {
|
property var apps: {
|
||||||
const pinnedApps = uniq((Config.dock.pinnedApps ?? []).map(normalizeId));
|
const pinnedApps = uniq((Config.dock.pinnedApps ?? []).map(normalizeId));
|
||||||
const openMap = buildOpenMap();
|
const openMap = buildOpenMap();
|
||||||
const openIds = [...openMap.keys()];
|
const openIds = [...openMap.keys()];
|
||||||
@@ -16,19 +16,33 @@ Singleton {
|
|||||||
|
|
||||||
const orderedUnpinned = sessionOrder.filter(id => openIds.includes(id) && !pinnedApps.includes(id)).concat(openIds.filter(id => !pinnedApps.includes(id) && !sessionOrder.includes(id)));
|
const orderedUnpinned = sessionOrder.filter(id => openIds.includes(id) && !pinnedApps.includes(id)).concat(openIds.filter(id => !pinnedApps.includes(id) && !sessionOrder.includes(id)));
|
||||||
|
|
||||||
return [].concat(pinnedApps.map(appId => appEntryComp.createObject(null, {
|
const out = [];
|
||||||
|
|
||||||
|
for (const appId of pinnedApps) {
|
||||||
|
out.push({
|
||||||
appId,
|
appId,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
toplevels: openMap.get(appId) ?? []
|
toplevels: openMap.get(appId) ?? []
|
||||||
}))).concat(pinnedApps.length > 0 ? [appEntryComp.createObject(null, {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pinnedApps.length > 0) {
|
||||||
|
out.push({
|
||||||
appId: root.separatorId,
|
appId: root.separatorId,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
toplevels: []
|
toplevels: []
|
||||||
})] : []).concat(orderedUnpinned.map(appId => appEntryComp.createObject(null, {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const appId of orderedUnpinned) {
|
||||||
|
out.push({
|
||||||
appId,
|
appId,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
toplevels: openMap.get(appId) ?? []
|
toplevels: openMap.get(appId) ?? []
|
||||||
})));
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
readonly property string separatorId: "__dock_separator__"
|
readonly property string separatorId: "__dock_separator__"
|
||||||
property var unpinnedOrder: []
|
property var unpinnedOrder: []
|
||||||
@@ -86,17 +100,4 @@ Singleton {
|
|||||||
function uniq(ids) {
|
function uniq(ids) {
|
||||||
return (ids ?? []).filter((id, i, arr) => id && arr.indexOf(id) === i);
|
return (ids ?? []).filter((id, i, arr) => id && arr.indexOf(id) === i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: appEntryComp
|
|
||||||
|
|
||||||
TaskbarAppEntry {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component TaskbarAppEntry: QtObject {
|
|
||||||
required property string appId
|
|
||||||
required property bool pinned
|
|
||||||
required property list<var> toplevels
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Item {
|
|||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Config.barConfig.border === 1 ? "transparent" : DynamicColors.palette.m3surface
|
color: !root.bar.isHovered && Config.barConfig.autoHide ? "transparent" : DynamicColors.palette.m3surface
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Item {
|
|||||||
readonly property int padding: Appearance.padding.small
|
readonly property int padding: Appearance.padding.small
|
||||||
required property var panels
|
required property var panels
|
||||||
readonly property int rounding: Appearance.rounding.large
|
readonly property int rounding: Appearance.rounding.large
|
||||||
|
required property ShellScreen screen
|
||||||
required property PersistentProperties visibilities
|
required property PersistentProperties visibilities
|
||||||
property var visualIds: []
|
property var visualIds: []
|
||||||
|
|
||||||
@@ -87,6 +88,7 @@ Item {
|
|||||||
readonly property string appId: modelData.appId
|
readonly property string appId: modelData.appId
|
||||||
readonly property bool isSeparator: appId === TaskbarApps.separatorId
|
readonly property bool isSeparator: appId === TaskbarApps.separatorId
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
property bool removing: false
|
||||||
|
|
||||||
function previewReorder(drag) {
|
function previewReorder(drag) {
|
||||||
const source = drag.source;
|
const source = drag.source;
|
||||||
@@ -102,12 +104,52 @@ Item {
|
|||||||
root.previewVisualMove(from, hovered, drag.x < width / 2);
|
root.previewVisualMove(from, hovered, drag.x < width / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
height: Config.dock.height
|
function startDetachedRemove() {
|
||||||
width: isSeparator ? 1 : Config.dock.height
|
const p = mapToItem(removalLayer, 0, 0);
|
||||||
|
|
||||||
|
removing = true;
|
||||||
|
ListView.delayRemove = true;
|
||||||
|
|
||||||
|
parent = removalLayer;
|
||||||
|
x = p.x;
|
||||||
|
y = p.y;
|
||||||
|
|
||||||
|
removeAnim.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
height: Config.dock.height
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
width: isSeparator ? 1 : Config.dock.height
|
||||||
|
z: removing ? 1 : 0
|
||||||
|
|
||||||
|
ListView.onRemove: startDetachedRemove()
|
||||||
onEntered: drag => previewReorder(drag)
|
onEntered: drag => previewReorder(drag)
|
||||||
onPositionChanged: drag => previewReorder(drag)
|
onPositionChanged: drag => previewReorder(drag)
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: removeAnim
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
property: "opacity"
|
||||||
|
target: slot
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
property: "scale"
|
||||||
|
target: slot
|
||||||
|
to: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptAction {
|
||||||
|
script: {
|
||||||
|
slot.ListView.delayRemove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DockAppButton {
|
DockAppButton {
|
||||||
id: button
|
id: button
|
||||||
|
|
||||||
@@ -121,7 +163,7 @@ Item {
|
|||||||
DragHandler {
|
DragHandler {
|
||||||
id: dragHandler
|
id: dragHandler
|
||||||
|
|
||||||
enabled: !slot.isSeparator
|
enabled: !slot.isSeparator && !slot.removing
|
||||||
grabPermissions: PointerHandler.CanTakeOverFromAnything
|
grabPermissions: PointerHandler.CanTakeOverFromAnything
|
||||||
target: null
|
target: null
|
||||||
xAxis.enabled: true
|
xAxis.enabled: true
|
||||||
@@ -160,24 +202,72 @@ Item {
|
|||||||
CustomListView {
|
CustomListView {
|
||||||
id: dockRow
|
id: dockRow
|
||||||
|
|
||||||
anchors.centerIn: parent
|
property bool enableAddAnimation: false
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.small
|
||||||
|
anchors.top: parent.top
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
implicitHeight: Config.dock.height
|
height: Config.dock.height
|
||||||
implicitWidth: root.dockContentWidth
|
implicitWidth: root.dockContentWidth + Config.dock.height
|
||||||
interactive: !root.dragActive
|
interactive: !root.dragActive
|
||||||
model: visualModel
|
model: visualModel
|
||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
spacing: Appearance.padding.smaller
|
spacing: Appearance.padding.smaller
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
add: Transition {
|
||||||
|
ParallelAnimation {
|
||||||
Anim {
|
Anim {
|
||||||
|
duration: dockRow.enableAddAnimation ? Appearance.anim.durations.normal : 0
|
||||||
|
from: 0
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
duration: dockRow.enableAddAnimation ? Appearance.anim.durations.normal : 0
|
||||||
|
from: 0.5
|
||||||
|
property: "scale"
|
||||||
|
to: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moveDisplaced: Transition {
|
}
|
||||||
|
displaced: Transition {
|
||||||
Anim {
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
properties: "x,y"
|
properties: "x,y"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
move: Transition {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
properties: "x,y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove: Transition {
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
property: "scale"
|
||||||
|
to: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Qt.callLater(() => enableAddAnimation = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: removalLayer
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 9998
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ Item {
|
|||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
visible: height > 0
|
visible: height > 0
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onShouldBeActiveChanged: {
|
onShouldBeActiveChanged: {
|
||||||
if (shouldBeActive) {
|
if (shouldBeActive) {
|
||||||
timer.stop();
|
timer.stop();
|
||||||
@@ -84,12 +90,13 @@ Item {
|
|||||||
id: content
|
id: content
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
panels: root.panels
|
panels: root.panels
|
||||||
|
screen: root.screen
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
|
|
||||||
Component.onCompleted: root.contentHeight = implicitHeight
|
Component.onCompleted: root.contentHeight = implicitHeight
|
||||||
|
|||||||
Reference in New Issue
Block a user