refactor(island): performance & stability improvements

- update qml-common
This commit is contained in:
purian23
2026-10-12 23:13:42 -04:00
parent d041ffccb0
commit eda2f4459b
12 changed files with 122 additions and 126 deletions
Generated
+3 -3
View File
@@ -3,11 +3,11 @@
"dank-qml-common": {
"flake": false,
"locked": {
"lastModified": 1791820510,
"narHash": "sha256-aDpJTeAE89k0zN584vponqj99QgiHK9gNoTZo3qiLWM=",
"lastModified": 1791861042,
"narHash": "sha256-Wop1FPbCObjcDAHjo2fWTVBJGKPihhcohN8YTM3Jw5Y=",
"owner": "AvengeMedia",
"repo": "dank-qml-common",
"rev": "ea058153e7362c13585ec3af72f19b666dfffdc0",
"rev": "5a0a6b1e6049bee316e5055d7fa6d9bb255e102e",
"type": "github"
},
"original": {
-8
View File
@@ -31,12 +31,4 @@ Singleton {
readonly property var expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
readonly property var expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
readonly property var expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
// Expressive spatial spring presets ([stiffness, damping], unit mass) tuned to a
// 500ms reference transition; runtime values scale via Theme.springPreset().
readonly property var spatialSprings: {
"expressive": [560, 37],
"fast": [220, 23],
"default": [100, 16]
}
}
+29 -13
View File
@@ -27,6 +27,7 @@ Item {
property var collapsedSections: ({})
property bool keyboardNavigationActive: false
property bool active: false
property bool retainResultsOnClose: false
property var _modeSectionsCache: ({})
property bool _queryDrivenSearch: false
property bool _diskCacheConsumed: false
@@ -183,9 +184,14 @@ Item {
} else {
SessionData.addLauncherHistory(searchQuery, explicitQuerySession);
}
_cancelSearch();
selectedFlatIndex = -1;
selectedItem = null;
keyboardNavigationActive = false;
if (retainResultsOnClose && searchMode !== "dmenu")
return;
sections = [];
flatModel = [];
selectedItem = null;
_clearModeCache();
}
@@ -247,9 +253,9 @@ Item {
readonly property int searchCacheVersion: AppSearchService.cacheVersion
onSearchCacheVersionChanged: {
_clearModeCache();
if (!active)
return;
_clearModeCache();
if (searchMode === "apps") {
_loadAppCategories();
performSearch();
@@ -542,6 +548,19 @@ Item {
property bool _searchPending: false
function _cancelSearch() {
_searchVersion++;
searchDebounce.stop();
pluginPhaseTimer.stop();
fileSearchDebounce.stop();
_searchPending = false;
_pluginPhasePending = false;
_pluginPhaseForceFirst = false;
_phase1Items = [];
isSearching = false;
isFileSearching = false;
}
// Leading-edge debounce: search immediately when idle, coalesce bursts
function requestSearch() {
if (searchDebounce.running) {
@@ -673,11 +692,11 @@ Item {
}
function reset() {
_cancelSearch();
searchQuery = "";
searchMode = "all";
previousSearchMode = "all";
autoSwitchedToFiles = false;
isFileSearching = false;
fileSearchType = "all";
fileSearchExt = "";
fileSearchFolder = "";
@@ -686,7 +705,6 @@ Item {
flatModel = [];
selectedFlatIndex = 0;
selectedItem = null;
isSearching = false;
activePluginId = "";
activePluginName = "";
activePluginCategories = [];
@@ -697,12 +715,10 @@ Item {
collapsedSections = {};
_clearModeCache();
_queryDrivenSearch = false;
_pluginPhasePending = false;
_pluginPhaseForceFirst = false;
_phase1Items = [];
}
function openSession(query, explicitQuery, mode, searchWhenEmpty) {
_cancelSearch();
if (mode && mode.indexOf("dmenu:") === 0) {
_startDmenuSession(mode.slice("dmenu:".length));
return;
@@ -716,7 +732,6 @@ Item {
historyIndex = -1;
previousSearchMode = "all";
autoSwitchedToFiles = false;
isFileSearching = false;
fileSearchType = SessionData.launcherLastFileSearchType || "all";
fileSearchExt = "";
fileSearchFolder = "";
@@ -729,9 +744,6 @@ Item {
selectedFlatIndex = 0;
selectedItem = null;
_queryDrivenSearch = false;
_pluginPhasePending = false;
_pluginPhaseForceFirst = false;
_phase1Items = [];
searchMode = mode;
if (query) {
setSearchQuery(query);
@@ -1528,7 +1540,7 @@ Item {
function _performPluginPhase() {
_pluginPhasePending = false;
if (searchMode !== "all")
if (!active || searchMode !== "all")
return;
if (searchQuery && searchQuery.length < 2)
return;
@@ -1671,7 +1683,7 @@ Item {
}
function performFileSearch() {
if (!DSearchService.dsearchAvailable)
if (!active || !DSearchService.dsearchAvailable)
return;
var fileQuery = "";
var effectiveType = fileSearchType || "all";
@@ -1706,6 +1718,8 @@ Item {
var queryTypes = splitBothTypes ? ["file", "dir"] : [effectiveType];
var pending = queryTypes.length;
var aggregatedItems = [];
const currentVersion = _searchVersion;
const currentMode = searchMode;
for (var t = 0; t < queryTypes.length; t++) {
var queryType = queryTypes[t];
@@ -1727,6 +1741,8 @@ Item {
}
DSearchService.search(fileQuery, params, function (response) {
if (!root.active || currentVersion !== root._searchVersion || currentMode !== root.searchMode)
return;
pending--;
if (!response.error) {
var hits = response.result?.hits || [];
@@ -492,49 +492,6 @@ Item {
}
}
Rectangle {
id: bottomShadow
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomSectionHeader.visible ? bottomSectionHeader.height : 0
height: Theme.spacingXL
z: 100
visible: {
if (BlurService.enabled)
return false;
if (mainListView.contentHeight <= mainListView.height)
return false;
var atBottom = mainListView.contentY >= mainListView.contentHeight - mainListView.height + mainListView.originY - Theme.spacingXS;
if (atBottom)
return false;
var flatModel = root.controller?.flatModel;
if (!flatModel || flatModel.length === 0)
return false;
var lastItemIdx = -1;
for (var i = flatModel.length - 1; i >= 0; i--) {
if (!flatModel[i].isHeader) {
lastItemIdx = i;
break;
}
}
if (lastItemIdx >= 0 && root.controller?.selectedFlatIndex === lastItemIdx)
return false;
return true;
}
gradient: Gradient {
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 1.0
color: Theme.readableSurface
}
}
}
Rectangle {
id: stickyHeader
anchors.left: parent.left
@@ -22,6 +22,8 @@ FocusScope {
property bool showResultsWithoutQuery: false
property bool suspendSearchUpdates: false
property real maxResultsHeight: 0
// Off for fixed-height hosts, where the resize would only slide the bottom fade.
property bool resizeAnimated: true
property real resultsInset: LauncherMetrics.spotlightInset
property bool editMode: false
@@ -447,7 +449,7 @@ FocusScope {
visible: !root.editMode
Behavior on height {
enabled: root._animateResize
enabled: root.resizeAnimated && root._animateResize
NumberAnimation {
duration: root._resizeDuration
easing.type: Easing.BezierSpline
@@ -25,18 +25,22 @@ FocusScope {
}
function initializeSession() {
const targetQuery = root.controller.launcherPendingQuery || (SettingsData.rememberLastQuery ? (SessionData.launcherLastQuery || "") : "");
const pendingQuery = root.controller.launcherPendingQuery;
const targetQuery = pendingQuery || (SettingsData.rememberLastQuery ? (SessionData.launcherLastQuery || "") : "");
const targetMode = root.controller.launcherPendingMode || SessionData.getLauncherRestoreMode();
// Used once: the face stays loaded between opens.
root.controller.launcherPendingQuery = "";
root.controller.launcherPendingMode = "";
launcherContent.closeTransientUi?.();
launcherContent.suspendSearchUpdates = true;
launcherContent.searchField.text = targetQuery;
launcherContent.suspendSearchUpdates = false;
launcherController.openSession(targetQuery, !!root.controller.launcherPendingQuery, targetMode, true);
launcherController.openSession(targetQuery, !!pendingQuery, targetMode, true);
launcherContent.resetScroll();
root.focusFace();
if (!root.controller.launcherPendingQuery) {
if (!pendingQuery) {
launcherContent.searchField.selectAll();
return;
}
@@ -73,6 +77,7 @@ FocusScope {
controllerOverride: root.launcherController
transientSurfaceTracker: root.transientSurfaceTracker
showResultsWithoutQuery: true
resizeAnimated: false
maxResultsHeight: Math.max(120, root.controller.launcherExpandedTarget.height - Theme.spacingM - root.bottomInset - launcherContent.searchAreaHeight - launcherContent.actionPanelHeight)
}
@@ -81,9 +86,10 @@ FocusScope {
Connections {
target: root.controller
// Synchronous so the results are ready before the first morph frame.
function onSessionStarted(activityId) {
if (activityId === "launcher")
Qt.callLater(root.initializeSession);
root.initializeSession();
}
}
@@ -422,6 +422,7 @@ Item {
id: contentHost
controller: root.controller
surfaceColor: root.effectiveSurfaceColor
freeMode: root.freeMode
compactFaceOverride: root.compactFaceOverride
resolveTarget: target => root.resolveTarget(target)
@@ -445,6 +445,7 @@ Item {
id: launcherController
active: controller.launcherSessionActive
retainResultsOnClose: true
viewModeContext: "spotlight"
forceLinearNavigation: true
}
@@ -22,6 +22,8 @@ Item {
required property bool expanded
required property bool pointerInside
required property string activityId
// The colour painted behind the faces, so edge fades blend into it.
required property color surfaceColor
property bool freeMode: false
property Component compactFaceOverride: null
// Hosted islands fold the slot anchor into every target; the face must pin to the same resolved slot.
@@ -58,12 +60,10 @@ Item {
readonly property bool mediaSurfaceActive: root.surfaceActive("media")
readonly property bool systemSurfaceActive: root.controller.systemActivities.some(id => root.surfaceActive(id))
function systemCompactOpacity() {
return Math.max(0, ...root.controller.systemActivities.map(id => root.compactOpacity(id)));
}
function systemExpandedOpacity() {
return Math.max(0, ...root.controller.systemActivities.map(id => root.expandedOpacity(id)));
function systemOpacity(opacityFor) {
const incoming = root.controller.isSystemActivity(root.renderedActivity) ? opacityFor(root.renderedActivity) : 0;
const outgoing = root.controller.isSystemActivity(root.outgoingActivity) ? opacityFor(root.outgoingActivity) : 0;
return Math.max(incoming, outgoing);
}
property string renderedActivity: "home"
@@ -174,14 +174,25 @@ Item {
}
}
// Where a resolved target sits at rest, in host coordinates.
function slotAlong(target) {
return root.controller.isVertical ? (root.hostHeight - target.height) / 2 + target.offsetAlong : (root.hostWidth - target.width) / 2 + target.offsetAlong;
}
function slotCross(target) {
if (!root.controller.farEdge)
return target.offsetCross;
return root.controller.isVertical ? root.hostWidth - target.offsetCross - target.width : root.hostHeight - target.offsetCross - target.height;
}
// Pinned to the compact target's own screen slot so the face holds still while the island morphs.
component CompactFace: Loader {
required property string activity
required property Component face
readonly property var target: root.resolveTarget(root.controller.compactTargetFor(activity))
readonly property bool isVertical: root.controller.isVertical
readonly property real alongPos: isVertical ? Math.round((root.hostHeight - target.height) / 2 + target.offsetAlong) - Math.round(root.islandY) : Math.round((root.hostWidth - target.width) / 2 + target.offsetAlong) - Math.round(root.islandX)
readonly property real crossPos: isVertical ? Math.round((parent.width - width) / 2) : Math.round((parent.height - height) / 2)
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(target)) - Math.round(isVertical ? root.islandY : root.islandX)
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(target)) - Math.round(isVertical ? root.islandX : root.islandY)
x: root.freeMode ? Math.round((parent.width - width) / 2) : (isVertical ? crossPos : alongPos)
y: root.freeMode ? Math.round((parent.height - height) / 2) : (isVertical ? alongPos : crossPos)
@@ -189,19 +200,26 @@ Item {
height: target.height
asynchronous: false
sourceComponent: root.compactFaceOverride && root.controller.usesDotFace(activity) ? root.compactFaceOverride : face
opacity: !active ? 0 : root.controller.isSystemActivity(activity) ? root.systemOpacity(root.compactOpacity) : root.compactOpacity(activity)
visible: opacity > 0.001
enabled: opacity >= 0.5
}
// Pinned to the sheet's settled slot so the content holds still while the island morphs.
component ExpandedFace: Loader {
required property string activity
readonly property var target: root.controller.expandedTargetFor(activity)
readonly property var slot: root.resolveTarget(target)
readonly property bool isVertical: root.controller.isVertical
readonly property real inset: root.controller.farEdge ? 0 : root.expandedInset
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(slot)) - Math.round(isVertical ? root.islandY : root.islandX)
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(slot)) - Math.round(isVertical ? root.islandX : root.islandY) + inset
x: isVertical && !root.controller.farEdge ? root.expandedInset : 0
y: !isVertical && !root.controller.farEdge ? root.expandedInset : 0
x: isVertical ? crossPos : alongPos
y: isVertical ? alongPos : crossPos
width: target.width
height: target.height
opacity: !active ? 0 : root.controller.isSystemActivity(activity) ? root.systemOpacity(root.expandedOpacity) : root.expandedOpacity(activity)
visible: opacity > 0.001
enabled: opacity >= 0.5
}
@@ -210,7 +228,6 @@ Item {
active: true
activity: "home"
face: root.homeCompactComponent
opacity: root.compactOpacity("home")
}
ExpandedFace {
@@ -220,14 +237,12 @@ Item {
active: root.homeExpandedTouched
asynchronous: true
sourceComponent: root.homeExpandedComponent
opacity: root.expandedOpacity("home")
}
CompactFace {
active: root.mediaSurfaceActive
activity: "media"
face: root.mediaCompactComponent
opacity: root.compactOpacity("media")
}
ExpandedFace {
@@ -237,14 +252,12 @@ Item {
active: root.mediaSurfaceActive && (root.expanded || root.expandedFade > 0)
asynchronous: false
sourceComponent: root.mediaExpandedComponent
opacity: root.expandedOpacity("media")
}
CompactFace {
active: root.surfaceActive("launcher")
activity: "launcher"
face: root.launcherCompactComponent
opacity: root.compactOpacity("launcher")
}
ExpandedFace {
@@ -254,7 +267,6 @@ Item {
active: root.controller.visualsRequested("launcher")
asynchronous: true
sourceComponent: root.launcherExpandedComponent
opacity: root.expandedOpacity("launcher")
onEnabledChanged: {
if (enabled)
root.refocusLauncher();
@@ -269,7 +281,6 @@ Item {
active: root.surfaceActive("controlcenter")
activity: "controlcenter"
face: root.controlCenterCompactComponent
opacity: root.compactOpacity("controlcenter")
}
ExpandedFace {
@@ -278,14 +289,12 @@ Item {
active: root.controller.visualsRequested("controlcenter")
asynchronous: false
sourceComponent: root.controlCenterExpandedComponent
opacity: root.expandedOpacity("controlcenter")
}
CompactFace {
active: root.surfaceActive("wallpaper")
activity: "wallpaper"
face: root.wallpaperCompactComponent
opacity: root.compactOpacity("wallpaper")
}
ExpandedFace {
@@ -295,14 +304,12 @@ Item {
active: root.controller.visualsRequested("wallpaper")
asynchronous: true
sourceComponent: root.wallpaperExpandedComponent
opacity: root.expandedOpacity("wallpaper")
}
CompactFace {
active: root.surfaceActive("weather")
activity: "weather"
face: root.weatherCompactComponent
opacity: root.compactOpacity("weather")
}
ExpandedFace {
@@ -312,14 +319,12 @@ Item {
active: root.controller.visualsRequested("weather")
asynchronous: true
sourceComponent: root.weatherExpandedComponent
opacity: root.expandedOpacity("weather")
}
CompactFace {
active: root.surfaceActive("notificationcenter")
activity: "notificationcenter"
face: root.notificationCenterCompactComponent
opacity: root.compactOpacity("notificationcenter")
}
ExpandedFace {
@@ -329,14 +334,12 @@ Item {
active: root.controller.visualsRequested("notificationcenter")
asynchronous: true
sourceComponent: root.notificationCenterExpandedComponent
opacity: root.expandedOpacity("notificationcenter")
}
CompactFace {
active: root.surfaceActive("clipboard")
activity: "clipboard"
face: root.clipboardCompactComponent
opacity: root.compactOpacity("clipboard")
}
ExpandedFace {
@@ -346,14 +349,12 @@ Item {
active: root.controller.visualsRequested("clipboard")
asynchronous: true
sourceComponent: root.clipboardExpandedComponent
opacity: root.expandedOpacity("clipboard")
}
CompactFace {
active: root.surfaceActive("powermenu")
activity: "powermenu"
face: root.powerMenuCompactComponent
opacity: root.compactOpacity("powermenu")
}
ExpandedFace {
@@ -363,14 +364,12 @@ Item {
active: root.controller.visualsRequested("powermenu")
asynchronous: true
sourceComponent: root.powerMenuExpandedComponent
opacity: root.expandedOpacity("powermenu")
}
CompactFace {
active: root.systemSurfaceActive
activity: "volume"
face: root.systemCompactComponent
opacity: root.systemCompactOpacity()
}
ExpandedFace {
@@ -378,14 +377,12 @@ Item {
active: root.systemSurfaceActive && (root.expanded || root.expandedFade > 0)
asynchronous: false
sourceComponent: root.systemExpandedComponent
opacity: root.systemExpandedOpacity()
}
CompactFace {
active: root.surfaceActive("notification")
activity: "notification"
face: root.notificationCompactComponent
opacity: root.compactOpacity("notification")
}
ExpandedFace {
@@ -393,6 +390,5 @@ Item {
active: root.surfaceActive("notification") && (root.expanded || root.expandedFade > 0)
asynchronous: false
sourceComponent: root.notificationExpandedComponent
opacity: root.expandedOpacity("notification")
}
}
@@ -324,12 +324,17 @@ QtObject {
destinationRevision++;
}
// The launcher stays loaded once built, so reopening never waits on a rebuild.
function keepsVisuals(activityId) {
return activityId === "launcher" && destinationState[activityId].ready;
}
function releaseIdleVisuals() {
for (const id of destinations) {
if (expanded && activeActivity === id)
continue;
const entry = destinationState[id];
if (!entry.requested && !entry.ready)
if (root.keepsVisuals(id) || (!entry.requested && !entry.ready))
continue;
visualsReleaseTimer.restart();
return;
@@ -341,7 +346,7 @@ QtObject {
for (const id of destinations) {
if (expanded && activeActivity === id)
continue;
if (destinationState[id].pending)
if (destinationState[id].pending || root.keepsVisuals(id))
continue;
dropVisuals(id);
}
@@ -753,11 +758,21 @@ QtObject {
}
function requestLauncher(query, mode, shouldToggle) {
if (shouldToggle === true && activeActivity === "launcher" && expanded)
const open = activeActivity === "launcher" && expanded;
if (shouldToggle === true && open)
return requestCollapse();
launcherPendingQuery = query || "";
launcherPendingMode = mode || "";
return requestActivity("launcher", true, true);
const handled = requestActivity("launcher", true, true);
// An open launcher applies a new query or mode in place.
if (handled && open && (launcherPendingQuery || launcherPendingMode))
sessionStarted("launcher");
// Only a launcher that is still loading reads these later.
if (!destinationState.launcher.pending) {
launcherPendingQuery = "";
launcherPendingMode = "";
}
return handled;
}
function requestControlCenter(section, shouldToggle) {
@@ -148,27 +148,37 @@ QtObject {
const steps = Math.max(1, Math.ceil(frameTime / integrationStep));
const step = frameTime / steps;
const inverseMass = 1 / Math.max(0.001, mass);
const k = stiffness;
const c = damping;
const target = [targetWidth, targetHeight, targetOffsetAlong, targetOffsetCross, targetTopLeftRadius, targetTopRightRadius, targetBottomLeftRadius, targetBottomRightRadius];
const current = [currentWidth, currentHeight, currentOffsetAlong, currentOffsetCross, currentTopLeftRadius, currentTopRightRadius, currentBottomLeftRadius, currentBottomRightRadius];
const velocity = [velocityWidth, velocityHeight, velocityOffsetAlong, velocityOffsetCross, velocityTopLeftRadius, velocityTopRightRadius, velocityBottomLeftRadius, velocityBottomRightRadius];
// Integrate in locals and write once per frame; every property write re-runs its bindings.
for (let i = 0; i < steps; i++) {
velocityWidth += (stiffness * (targetWidth - currentWidth) - damping * velocityWidth) * inverseMass * step;
velocityHeight += (stiffness * (targetHeight - currentHeight) - damping * velocityHeight) * inverseMass * step;
velocityOffsetAlong += (stiffness * (targetOffsetAlong - currentOffsetAlong) - damping * velocityOffsetAlong) * inverseMass * step;
velocityOffsetCross += (stiffness * (targetOffsetCross - currentOffsetCross) - damping * velocityOffsetCross) * inverseMass * step;
velocityTopLeftRadius += (stiffness * (targetTopLeftRadius - currentTopLeftRadius) - damping * velocityTopLeftRadius) * inverseMass * step;
velocityTopRightRadius += (stiffness * (targetTopRightRadius - currentTopRightRadius) - damping * velocityTopRightRadius) * inverseMass * step;
velocityBottomLeftRadius += (stiffness * (targetBottomLeftRadius - currentBottomLeftRadius) - damping * velocityBottomLeftRadius) * inverseMass * step;
velocityBottomRightRadius += (stiffness * (targetBottomRightRadius - currentBottomRightRadius) - damping * velocityBottomRightRadius) * inverseMass * step;
currentWidth += velocityWidth * step;
currentHeight += velocityHeight * step;
currentOffsetAlong += velocityOffsetAlong * step;
currentOffsetCross += velocityOffsetCross * step;
currentTopLeftRadius += velocityTopLeftRadius * step;
currentTopRightRadius += velocityTopRightRadius * step;
currentBottomLeftRadius += velocityBottomLeftRadius * step;
currentBottomRightRadius += velocityBottomRightRadius * step;
for (let j = 0; j < 8; j++) {
velocity[j] += (k * (target[j] - current[j]) - c * velocity[j]) * inverseMass * step;
current[j] += velocity[j] * step;
}
}
velocityWidth = velocity[0];
velocityHeight = velocity[1];
velocityOffsetAlong = velocity[2];
velocityOffsetCross = velocity[3];
velocityTopLeftRadius = velocity[4];
velocityTopRightRadius = velocity[5];
velocityBottomLeftRadius = velocity[6];
velocityBottomRightRadius = velocity[7];
currentWidth = current[0];
currentHeight = current[1];
currentOffsetAlong = current[2];
currentOffsetCross = current[3];
currentTopLeftRadius = current[4];
currentTopRightRadius = current[5];
currentBottomLeftRadius = current[6];
currentBottomRightRadius = current[7];
if (isSettled())
settle();
else