mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-20 10:14:01 -04:00
401 lines
17 KiB
QML
401 lines
17 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Widgets
|
|
import qs.Common
|
|
import qs.Modules.Plugins
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
import qs.Widgets
|
|
|
|
BasePill {
|
|
id: root
|
|
|
|
property var widgetData: null
|
|
property bool compactMode: SettingsData.widgetOption("focusedWindow", widgetData, "focusedWindowCompactMode")
|
|
property bool showIcon: SettingsData.widgetOption("focusedWindow", widgetData, "focusedWindowShowIcon")
|
|
readonly property int maxWidth: {
|
|
const size = SettingsData.widgetOption("focusedWindow", widgetData, "focusedWindowSize");
|
|
switch (size) {
|
|
case 0:
|
|
return 288;
|
|
case 2:
|
|
return 656;
|
|
case 3:
|
|
return 856;
|
|
default:
|
|
return 456;
|
|
}
|
|
}
|
|
property real allottedPrimarySize: 0
|
|
property int availableWidth: allottedPrimarySize > 0 ? allottedPrimarySize : maxWidth
|
|
readonly property real naturalPrimarySize: isVerticalOrientation ? height : !hasWindowsOnCurrentWorkspace ? 0 : Theme.snap(Math.min(contentItem?.naturalWidth ?? 0, maxWidth - horizontalPadding * 2) + horizontalPadding * 2, dpr)
|
|
readonly property real minimumPrimarySize: isVerticalOrientation ? height : Math.min(naturalPrimarySize, Theme.snap((contentItem?.minimumWidth ?? 0) + horizontalPadding * 2, dpr))
|
|
readonly property real effectiveHorizontalWidth: Math.max(0, Math.min(maxWidth, availableWidth))
|
|
readonly property real effectiveHorizontalInnerWidth: Math.max(0, effectiveHorizontalWidth - horizontalPadding * 2)
|
|
property var activeWindow: null
|
|
property var activeDesktopEntry: null
|
|
property bool isHovered: mouseArea.containsMouse
|
|
|
|
readonly property string screenName: parentScreen?.name ?? ""
|
|
readonly property bool popoutVisible: focusedWindowPopoutLoader.item?.shouldBeVisible ?? false
|
|
|
|
function updateActiveWindow() {
|
|
activeWindow = CompositorService.activeWindowForScreen(parentScreen ? parentScreen.name : null, activeWindow, popoutVisible);
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
updateActiveWindow();
|
|
updateDesktopEntry();
|
|
}
|
|
|
|
readonly property Toplevel managerActiveToplevel: ToplevelManager.activeToplevel
|
|
|
|
onManagerActiveToplevelChanged: updateActiveWindow()
|
|
|
|
Connections {
|
|
target: CompositorService
|
|
function onToplevelsChanged() {
|
|
root.updateActiveWindow();
|
|
}
|
|
function onWorkspaceStateChanged() {
|
|
root.updateActiveWindow();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: DesktopEntries
|
|
function onApplicationsChanged() {
|
|
root.updateDesktopEntry();
|
|
}
|
|
}
|
|
|
|
function syncPopoutState() {
|
|
const popout = focusedWindowPopoutLoader.item;
|
|
if (!popout || !activeWindow || !root.parentScreen)
|
|
return;
|
|
popout.currentWindow = activeWindow;
|
|
popout.processId = CompositorService.windowPid(activeWindow);
|
|
root.positionPopout(popout);
|
|
}
|
|
|
|
onActiveWindowChanged: {
|
|
updateDesktopEntry();
|
|
if (focusedWindowPopoutLoader.item?.shouldBeVisible) {
|
|
if (activeWindow) {
|
|
syncPopoutState();
|
|
Qt.callLater(() => root.syncPopoutState());
|
|
} else {
|
|
focusedWindowPopoutLoader.item.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
readonly property var settingsAppIdSubstitutions: SettingsData.appIdSubstitutions
|
|
|
|
onSettingsAppIdSubstitutionsChanged: updateDesktopEntry()
|
|
|
|
function updateDesktopEntry() {
|
|
if (activeWindow && activeWindow.appId) {
|
|
const moddedId = Paths.moddedAppId(activeWindow.appId);
|
|
activeDesktopEntry = DesktopEntries.heuristicLookup(moddedId);
|
|
} else {
|
|
activeDesktopEntry = null;
|
|
}
|
|
}
|
|
readonly property bool hasWindowsOnCurrentWorkspace: {
|
|
CompositorService.windowStateRevision;
|
|
if (!activeWindow || !(activeWindow.title || activeWindow.appId))
|
|
return false;
|
|
return CompositorService.windowOnActiveWorkspace(screenName, activeWindow, popoutVisible);
|
|
}
|
|
|
|
width: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? barThickness : (effectiveHorizontalInnerWidth > 0 ? visualWidth : 0)) : 0
|
|
height: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? visualHeight : barThickness) : 0
|
|
visible: hasWindowsOnCurrentWorkspace && (isVerticalOrientation || effectiveHorizontalInnerWidth > 0)
|
|
|
|
content: Component {
|
|
Item {
|
|
readonly property bool iconShown: horizontalAppIcon.visible || horizontalSteamIcon.visible
|
|
readonly property real appTextNatural: appText.visible ? Math.min(appText.implicitWidth, root.compactMode ? 80 : 180) : 0
|
|
readonly property real naturalWidth: {
|
|
const parts = [];
|
|
if (iconShown)
|
|
parts.push(contentRow.iconSize);
|
|
if (appText.visible)
|
|
parts.push(appTextNatural);
|
|
if (appSeparator.visible)
|
|
parts.push(appSeparator.width);
|
|
if (titleText.visible)
|
|
parts.push(titleText.implicitWidth);
|
|
return parts.reduce((sum, part) => sum + part, 0) + Math.max(0, parts.length - 1) * contentRow.spacing;
|
|
}
|
|
readonly property real minimumWidth: (iconShown ? contentRow.iconSize + contentRow.spacing : 0) + Math.min(48, appText.visible ? appTextNatural : titleText.visible ? titleText.implicitWidth : 0)
|
|
implicitWidth: {
|
|
if (!root.hasWindowsOnCurrentWorkspace)
|
|
return 0;
|
|
if (root.isVerticalOrientation)
|
|
return root.contentThickness;
|
|
return Math.min(contentRow.implicitWidth, root.effectiveHorizontalInnerWidth);
|
|
}
|
|
width: root.isVerticalOrientation ? root.contentThickness : Math.min(implicitWidth, root.effectiveHorizontalInnerWidth)
|
|
implicitHeight: root.contentThickness
|
|
clip: false
|
|
|
|
IconImage {
|
|
id: appIcon
|
|
anchors.centerIn: parent
|
|
width: 18
|
|
height: 18
|
|
visible: root.isVerticalOrientation && activeWindow && status === Image.Ready
|
|
source: {
|
|
if (!activeWindow || !activeWindow.appId)
|
|
return "";
|
|
return Paths.getAppIcon(activeWindow.appId, activeDesktopEntry);
|
|
}
|
|
smooth: true
|
|
mipmap: true
|
|
asynchronous: true
|
|
layer.enabled: activeWindow && (activeWindow.appId === "org.quickshell" || activeWindow.appId === "com.danklinux.dms")
|
|
layer.smooth: true
|
|
layer.mipmap: true
|
|
layer.effect: MultiEffect {
|
|
saturation: 0
|
|
colorization: 1
|
|
colorizationColor: Theme.primary
|
|
}
|
|
}
|
|
|
|
DIcon {
|
|
anchors.centerIn: parent
|
|
size: 18
|
|
name: "sports_esports"
|
|
color: root.contentColor
|
|
visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && Paths.isSteamApp(activeWindow.appId)
|
|
}
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && !Paths.isSteamApp(activeWindow.appId)
|
|
text: {
|
|
if (!activeWindow || !activeWindow.appId)
|
|
return "?";
|
|
const appName = Paths.getAppName(activeWindow.appId, activeDesktopEntry);
|
|
return appName.charAt(0).toUpperCase();
|
|
}
|
|
font.pixelSize: 10
|
|
color: root.contentColor
|
|
}
|
|
|
|
Item {
|
|
clip: true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: parent.width
|
|
height: root.barThickness
|
|
|
|
Row {
|
|
id: contentRow
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
spacing: Theme.spacingS
|
|
visible: !root.isVerticalOrientation
|
|
|
|
readonly property real iconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
|
|
IconImage {
|
|
id: horizontalAppIcon
|
|
width: contentRow.iconSize
|
|
height: contentRow.iconSize
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: root.showIcon && activeWindow && status === Image.Ready
|
|
source: {
|
|
if (!activeWindow || !activeWindow.appId)
|
|
return "";
|
|
return Paths.getAppIcon(activeWindow.appId, activeDesktopEntry);
|
|
}
|
|
smooth: true
|
|
mipmap: true
|
|
asynchronous: true
|
|
layer.enabled: activeWindow && (activeWindow.appId === "org.quickshell" || activeWindow.appId === "com.danklinux.dms")
|
|
layer.smooth: true
|
|
layer.mipmap: true
|
|
layer.effect: MultiEffect {
|
|
saturation: 0
|
|
colorization: 1
|
|
colorizationColor: Theme.primary
|
|
}
|
|
}
|
|
|
|
DIcon {
|
|
id: horizontalSteamIcon
|
|
width: contentRow.iconSize
|
|
size: contentRow.iconSize
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
name: "sports_esports"
|
|
color: root.contentColor
|
|
visible: root.showIcon && activeWindow && activeWindow.appId && horizontalAppIcon.status !== Image.Ready && Paths.isSteamApp(activeWindow.appId)
|
|
}
|
|
|
|
StyledText {
|
|
id: appText
|
|
text: {
|
|
if (compactMode || !activeWindow || !activeWindow.appId)
|
|
return "";
|
|
return Paths.getAppName(activeWindow.appId, activeDesktopEntry);
|
|
}
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: root.contentColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
width: {
|
|
const sp = contentRow.spacing;
|
|
let used = 0;
|
|
if (horizontalAppIcon.visible)
|
|
used += horizontalAppIcon.width + sp;
|
|
else if (horizontalSteamIcon.visible)
|
|
used += horizontalSteamIcon.width + sp;
|
|
const budget = Math.max(0, root.effectiveHorizontalInnerWidth - used);
|
|
return Math.min(implicitWidth, compactMode ? 80 : 180, budget);
|
|
}
|
|
visible: text.length > 0
|
|
}
|
|
|
|
StyledText {
|
|
id: appSeparator
|
|
text: compactMode ? "" : "•"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.outlineButton
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: !compactMode && appText.text && titleText.text
|
|
}
|
|
|
|
StyledText {
|
|
id: titleText
|
|
text: {
|
|
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
|
const appName = appText.text;
|
|
|
|
if (compactMode) {
|
|
if (!title || title === appName)
|
|
return title || appName;
|
|
if (title.endsWith(appName))
|
|
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "") || appName;
|
|
return title;
|
|
}
|
|
|
|
if (!title || !appName)
|
|
return title;
|
|
|
|
if (title.endsWith(appName))
|
|
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
|
|
|
return title;
|
|
}
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: root.contentColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
width: {
|
|
const sp = contentRow.spacing;
|
|
let used = 0;
|
|
if (horizontalAppIcon.visible)
|
|
used += horizontalAppIcon.width + sp;
|
|
else if (horizontalSteamIcon.visible)
|
|
used += horizontalSteamIcon.width + sp;
|
|
if (appText.visible)
|
|
used += appText.width + sp;
|
|
if (appSeparator.visible)
|
|
used += appSeparator.width + sp;
|
|
const budget = root.effectiveHorizontalInnerWidth - used;
|
|
return Math.min(implicitWidth, Math.max(0, budget));
|
|
}
|
|
visible: text.length > 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: mouseArea
|
|
x: -root.leftMargin
|
|
y: -root.topMargin
|
|
width: root.width + root.leftMargin + root.rightMargin
|
|
height: root.height + root.topMargin + root.bottomMargin
|
|
hoverEnabled: root.isVerticalOrientation
|
|
cursorShape: Qt.PointingHandCursor
|
|
onEntered: {
|
|
if (root.isVerticalOrientation && activeWindow && activeWindow.appId && root.parentScreen) {
|
|
tooltipLoader.active = true;
|
|
if (tooltipLoader.item) {
|
|
const localPos = mapToItem(null, width / 2, height / 2);
|
|
const currentScreen = root.parentScreen;
|
|
const adjustedY = localPos.y + root.minTooltipY;
|
|
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (currentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS);
|
|
|
|
const appName = Paths.getAppName(activeWindow.appId, activeDesktopEntry);
|
|
const title = activeWindow.title || "";
|
|
const tooltipText = appName + (title ? " • " + title : "");
|
|
|
|
const isLeft = root.axis?.edge === "left";
|
|
tooltipLoader.item.show(tooltipText, tooltipX, adjustedY, currentScreen, isLeft, !isLeft);
|
|
}
|
|
}
|
|
}
|
|
onExited: {
|
|
if (tooltipLoader.item) {
|
|
tooltipLoader.item.hide();
|
|
}
|
|
tooltipLoader.active = false;
|
|
}
|
|
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
if (!activeWindow || !root.parentScreen)
|
|
return;
|
|
if (tooltipLoader.item)
|
|
tooltipLoader.item.hide();
|
|
tooltipLoader.active = false;
|
|
|
|
// No context menu from inside the overflow popup; behave like a task switcher.
|
|
const owner = BarWidgetService.registrationForItem(root)?.context?.owner;
|
|
if (owner?.overflowAnchor) {
|
|
CompositorService.activateToplevel(activeWindow);
|
|
owner.overflowSurface?.close();
|
|
return;
|
|
}
|
|
|
|
focusedWindowPopoutLoader.active = true;
|
|
if (!focusedWindowPopoutLoader.item)
|
|
return;
|
|
|
|
root.syncPopoutState();
|
|
focusedWindowPopoutLoader.item.toggle();
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: tooltipLoader
|
|
active: false
|
|
sourceComponent: DTooltip {}
|
|
}
|
|
|
|
Loader {
|
|
id: focusedWindowPopoutLoader
|
|
active: false
|
|
sourceComponent: FocusedWindowContextMenu {
|
|
onPopoutClosed: root.updateActiveWindow()
|
|
}
|
|
}
|
|
|
|
onPopoutVisibleChanged: {
|
|
if (!popoutVisible)
|
|
updateActiveWindow();
|
|
}
|
|
}
|