From 4fcc12444f646b6c9f62f389d4698f9aba12de5c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 20 Mar 2026 23:38:14 +0100 Subject: [PATCH] hefty: bar: window info --- .../quickshell/ii/modules/common/Config.qml | 2 +- .../ii/modules/common/config/HeftyConfig.qml | 2 +- .../widgets/VisuallyCenteredStyledText.qml | 3 + .../hefty/topLayer/bar/HBarContent.qml | 24 +- .../bar/HBarUserFallbackComponentRepeater.qml | 8 +- .../topLayer/bar/widgets/HWindowInfo.qml | 282 ++++++++++++++++++ 6 files changed, 313 insertions(+), 8 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWindowInfo.qml diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index f658fa395..a0fed3a99 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -159,8 +159,8 @@ Singleton { property JsonObject apps: JsonObject { property string bluetooth: "kcmshell6 kcm_bluetooth" property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'" - property string network: "kcmshell6 kcm_networkmanagement" property string manageUser: "kcmshell6 kcm_users" + property string network: "kcmshell6 kcm_networkmanagement" property string networkEthernet: "kcmshell6 kcm_networkmanagement" property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions diff --git a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml index 2275e3704..5bd70b28f 100644 --- a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml +++ b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml @@ -4,7 +4,7 @@ import Quickshell.Io JsonObject { property JsonObject bar: JsonObject { - property list leftWidgets: ["HLeftSidebarButton"] + property list leftWidgets: ["HLeftSidebarButton", "HWindowInfo"] property list centerLeftWidgets: ["HTime"] property list centerWidgets: ["HWorkspaces"] property list centerRightWidgets: ["HResources"] diff --git a/dots/.config/quickshell/ii/modules/common/widgets/VisuallyCenteredStyledText.qml b/dots/.config/quickshell/ii/modules/common/widgets/VisuallyCenteredStyledText.qml index 1390d2caa..6391be5df 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/VisuallyCenteredStyledText.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/VisuallyCenteredStyledText.qml @@ -11,6 +11,9 @@ Item { property alias verticalAlignment: textWidget.verticalAlignment property alias font: textWidget.font property alias color: textWidget.color + property alias elide: textWidget.elide + property alias wrapMode: textWidget.wrapMode + property alias animateChange: textWidget.animateChange // In many cases the baseline is a bit high to accomodate the dangling parts of "g" and "y", // making most text (especiall number-only text) not well-balanced. diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarContent.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarContent.qml index 7b1e5d6d3..18c3b4d30 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarContent.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarContent.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import qs.modules.common as C +import qs.modules.common.widgets as W Item { id: root @@ -19,10 +20,17 @@ Item { id: leftSide anchors.left: parent.left anchors.top: parent.top + anchors.right: !root.vertical ? centerLeftSide.left : parent.right + anchors.bottom: !root.vertical ? parent.bottom : centerLeftSide.top HBarUserFallbackComponentRepeater { componentNames: root.leftWidgets } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } } Side { @@ -56,12 +64,20 @@ Item { id: rightSide anchors.right: parent.right anchors.bottom: parent.bottom + anchors.left: !root.vertical ? centerRightSide.right : parent.left + anchors.top: !root.vertical ? parent.top : centerRightSide.bottom + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + HBarUserFallbackComponentRepeater { componentNames: root.rightWidgets } } - component Side: GridLayout { + component Side: W.BoxLayout { anchors { top: !root.vertical ? parent.top : undefined bottom: !root.vertical ? parent.bottom : undefined @@ -73,9 +89,7 @@ Item { rightMargin: root.spacing * !root.vertical } - columns: C.Config.options.bar.vertical ? 1 : -1 - property real spacing: root.spacing - columnSpacing: spacing - rowSpacing: spacing + vertical: C.Config.options.bar.vertical + spacing: root.spacing } } diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml index 7cc44ba89..f0720b829 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml @@ -1,5 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick +import QtQuick.Layouts import Quickshell import qs.modules.common as C import qs.modules.common.widgets as W @@ -48,6 +49,11 @@ Repeater { context: root.context property bool startSide: index === 0 property bool endSide: index === root.model.length - 1 + + Layout.fillWidth: item.Layout.fillWidth + Layout.fillHeight: item.Layout.fillHeight + Layout.maximumWidth: item.Layout.maximumWidth + Layout.maximumHeight: item.Layout.maximumHeight } } @@ -74,4 +80,4 @@ Repeater { } } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWindowInfo.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWindowInfo.qml new file mode 100644 index 000000000..2075d8b65 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWindowInfo.qml @@ -0,0 +1,282 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland + +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import ".." + +HBarWidgetWithPopout { + id: root + + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + readonly property var activeHyprlandClient: HyprlandData.clientForToplevel(activeWindow) + readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name + readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id) + readonly property bool hasFocusedWindow: (focusingThisMonitor && activeWindow?.activated && biggestWindow) ?? false + + readonly property string primaryText: { + if (root.hasFocusedWindow) + return root.activeWindow?.title; + return (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${root.monitor?.activeWorkspace?.id ?? 1}`; + } + readonly property string secondaryText: { + if (root.hasFocusedWindow && root.activeWindow?.appId != "" && root.activeWindow?.appId != primaryText) + return root.activeWindow?.appId; + return Translation.tr("Options") + } + + property real fontPixelSize: Appearance.font.pixelSize.smaller + + Layout.maximumWidth: implicitWidth + Layout.fillWidth: true + + popupContentWidth: popupContent.implicitWidth + popupContentHeight: popupContent.implicitHeight + + HBarWidgetContent { + id: contentRoot + Layout.fillWidth: true + Layout.fillHeight: true + vertical: root.vertical + atBottom: root.atBottom + contentImplicitWidth: winTitleContent.implicitWidth + contentImplicitHeight: winTitleContent.implicitHeight + showPopup: false + onClicked: root.showPopup = !root.showPopup; + + WinTitleContent { + id: winTitleContent + } + + WinOptionsPopup { + id: popupContent + anchors { + top: parent.top + topMargin: root.popupContentOffsetY + left: parent.left + leftMargin: root.popupContentOffsetX + } + shown: root.showPopup + } + } + + component WinTitleContent: BoxLayout { + anchors.fill: parent + vertical: root.vertical + spacing: 4 + + Item { + Layout.leftMargin: 4 * !root.vertical + Layout.topMargin: 3 * root.vertical + Layout.bottomMargin: 4 * root.vertical + Layout.alignment: Qt.AlignCenter + implicitWidth: appIcon.implicitWidth + implicitHeight: appIcon.implicitHeight + + AppIcon { + id: appIcon + anchors.centerIn: parent + opacity: 0 + source: Quickshell.iconPath(AppSearch.guessIcon(root.activeWindow?.appId), "image-missing") + implicitSize: 16 + animated: false + } + Circle { + id: iconMask + visible: false + layer.enabled: true + diameter: appIcon.implicitSize + } + Loader { + anchors.fill: appIcon + Colorizer { + id: renderedIcon + implicitWidth: appIcon.implicitWidth + implicitHeight: appIcon.implicitHeight + colorizationColor: Appearance.colors.colOnLayer0 + // colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5 + colorization: 1 + brightness: 0 + source: appIcon + + maskEnabled: true + maskSource: iconMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + + visible: root.activeWindow + } + } + + MaterialSymbol { + anchors.centerIn: parent + visible: !renderedIcon.visible + text: "overview_key" + iconSize: 16 + } + } + + Item { + visible: !root.vertical + Layout.rightMargin: 4 + Layout.alignment: Qt.AlignCenter + Layout.fillHeight: true + // No overflow + Layout.maximumWidth: implicitWidth + Layout.fillWidth: true + // Size + implicitWidth: winText.implicitWidth + implicitHeight: winText.implicitHeight + + FlyFadeEnterChoreographable { + anchors.fill: parent + progress: contentRoot.containsMouse ? 0 : 1 + reverseDirection: true + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + VisuallyCenteredStyledText { + id: winText + height: parent.height + width: parent.width + elide: Text.ElideRight + // Styles & text + font.pixelSize: root.fontPixelSize + text: root.primaryText + } + } + FlyFadeEnterChoreographable { + anchors.fill: parent + progress: contentRoot.containsMouse ? 1 : 0 + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + VisuallyCenteredStyledText { + height: parent.height + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + // Styles & text + font.pixelSize: root.fontPixelSize + text: root.secondaryText + } + } + } + } + + component WinOptionsPopup: ChoreographerLoader { + sourceComponent: ChoreographerGridLayout { + id: popupRoot + + columns: 3 + rowSpacing: 8 + columnSpacing: 6 + + FlyFadeEnterChoreographable { + Layout.fillWidth: true + Layout.columnSpan: 3 + StyledText { + width: parent.width + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: root.hasFocusedWindow ? Translation.tr("Window options") : Translation.tr("Launch") + // font.pixelSize: Appearance.font.pixelSize.title + } + } + + FlyFadeEnterChoreographable { + visible: !root.hasFocusedWindow + PopupLabeledIconButton { + materialSymbol: "terminal" + text: Translation.tr("Terminal") + onClicked: Quickshell.execDetached(["bash", "-c", Config.options.apps.terminal]); + } + } + + FlyFadeEnterChoreographable { + visible: !root.hasFocusedWindow + PopupLabeledIconButton { + materialSymbol: "files" + text: Translation.tr("Files") + onClicked: Qt.openUrlExternally(Directories.home); + } + } + + FlyFadeEnterChoreographable { + visible: !root.hasFocusedWindow + PopupLabeledIconButton { + materialSymbol: "language" + text: Translation.tr("Browser") + // Kinda hacky. Works with Google and DDG at least + onClicked: Qt.openUrlExternally(Config.options.search.engineBaseUrl); + } + } + + FlyFadeEnterChoreographable { + visible: root.hasFocusedWindow + PopupLabeledIconButton { + materialSymbol: "content_copy" + text: Translation.tr("Address") + onClicked: Quickshell.clipboardText = root.activeHyprlandClient.address + } + } + + FlyFadeEnterChoreographable { + visible: root.hasFocusedWindow + PopupLabeledIconButton { + property bool toFloat: !(root.activeHyprlandClient?.floating ?? false) + materialSymbol: toFloat ? "picture_in_picture_center" : "side_navigation" + text: toFloat ? Translation.tr("Float") : Translation.tr("Tile") + onClicked: { + Hyprland.dispatch(`togglefloating address:${root.activeHyprlandClient.address}`) + HyprlandData.updateWindowList() + } + } + } + + FlyFadeEnterChoreographable { + visible: root.hasFocusedWindow + PopupLabeledIconButton { + materialSymbol: "warning" + text: Translation.tr("Kill") + colBackground: Appearance.colors.colError + colForeground: Appearance.colors.colOnError + onClicked: { + Hyprland.dispatch(`killwindow address:${root.activeHyprlandClient.address}`) + HyprlandData.updateWindowList() + } + } + } + } + } + + component PopupLabeledIconButton: Column { + id: licobtn + property string materialSymbol: "circle" + property string text: "Label" + property alias colBackground: btn.colBackground + property alias colForeground: btn.colForeground + spacing: 4 + + signal clicked() + onClicked: root.showPopup = false + + StyledIconButton { + id: btn + implicitWidth: 70 + implicitHeight: 50 + text: licobtn.materialSymbol + iconSize: 24 + colBackground: Appearance.colors.colLayer4 + colForeground: Appearance.colors.colOnLayer4 + onClicked: licobtn.clicked() + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + text: licobtn.text + } + } +}