popout positioning fixes

This commit is contained in:
Zacharias-Brohn
2025-11-27 19:15:35 +01:00
parent fb864da6fb
commit 67ad544359
17 changed files with 201 additions and 93 deletions
+2 -2
View File
@@ -9,14 +9,14 @@ Shape {
required property Item bar required property Item bar
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 // anchors.margins: 8
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
Modules.Background { Modules.Background {
wrapper: root.panels.popouts wrapper: root.panels.popouts
startX: wrapper.x - rounding startX: wrapper.x - 8
startY: wrapper.y startY: wrapper.y
} }
} }
+4 -5
View File
@@ -13,7 +13,7 @@ Item {
readonly property alias popouts: popouts readonly property alias popouts: popouts
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 // anchors.margins: 8
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
Modules.Wrapper { Modules.Wrapper {
@@ -21,13 +21,12 @@ Item {
screen: root.screen screen: root.screen
anchors.top: parent.top
x: { x: {
const off = currentCenter - 8 - nonAnimWidth / 2; const off = currentCenter - nonAnimWidth / 2;
const diff = root.width - Math.floor(off + nonAnimWidth); const diff = root.width - Math.floor(off + nonAnimWidth);
if (diff < 0) if ( diff < 0 )
return off + diff; return off + diff;
return Math.floor(Math.max(off, 0)); return Math.floor( Math.max( off, 0 ));
} }
} }
} }
+41 -1
View File
@@ -14,11 +14,15 @@ Singleton {
readonly property var workspaces: Hyprland.workspaces readonly property var workspaces: Hyprland.workspaces
readonly property var monitors: Hyprland.monitors readonly property var monitors: Hyprland.monitors
readonly property HyprlandToplevel activeToplevel: Hyprland.activeToplevel?.wayland?.activated ? Hyprland.activeToplevel : null readonly property HyprlandToplevel activeToplevel: Hyprland.activeToplevel
readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace
readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor
readonly property int activeWsId: focusedWorkspace?.id ?? 1 readonly property int activeWsId: focusedWorkspace?.id ?? 1
property string activeName
property string applicationDir: "/usr/share/applications/"
property string desktopName: ""
readonly property HyprKeyboard keyboard: extras.devices.keyboards.find(kb => kb.main) ?? null readonly property HyprKeyboard keyboard: extras.devices.keyboards.find(kb => kb.main) ?? null
readonly property bool capsLock: keyboard?.capsLock ?? false readonly property bool capsLock: keyboard?.capsLock ?? false
readonly property bool numLock: keyboard?.numLock ?? false readonly property bool numLock: keyboard?.numLock ?? false
@@ -49,6 +53,19 @@ Singleton {
Component.onCompleted: reloadDynamicConfs() Component.onCompleted: reloadDynamicConfs()
function updateActiveWindow(): void {
root.desktopName = root.applicationDir + root.activeToplevel?.lastIpcObject.class + ".desktop";
}
Connections {
target: Hyprland
function onRawEvent( event: HyprlandEvent ): void {
if ( event.name === "activewindow" ) {
}
}
}
Connections { Connections {
target: Hyprland target: Hyprland
@@ -63,15 +80,38 @@ Singleton {
} else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) { } else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) {
Hyprland.refreshWorkspaces(); Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors(); Hyprland.refreshMonitors();
Qt.callLater( root.updateActiveWindow );
} else if (["openwindow", "closewindow", "movewindow"].includes(n)) { } else if (["openwindow", "closewindow", "movewindow"].includes(n)) {
Hyprland.refreshToplevels(); Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces(); Hyprland.refreshWorkspaces();
Qt.callLater( root.updateActiveWindow );
} else if (n.includes("mon")) { } else if (n.includes("mon")) {
Hyprland.refreshMonitors(); Hyprland.refreshMonitors();
Qt.callLater( root.updateActiveWindow );
} else if (n.includes("workspace")) { } else if (n.includes("workspace")) {
Hyprland.refreshWorkspaces(); Hyprland.refreshWorkspaces();
Qt.callLater( root.updateActiveWindow );
} else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) { } else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) {
Hyprland.refreshToplevels(); Hyprland.refreshToplevels();
Qt.callLater( root.updateActiveWindow );
}
}
}
FileView {
id: desktopEntryName
path: root.desktopName
onLoaded: {
const lines = text().split( "\n" );
for ( const line of lines ) {
if ( line.startsWith( "Name=" )) {
let name = line.replace( "Name=", "" );
let caseFix = name[ 0 ].toUpperCase() + name.slice( 1 );
root.activeName = caseFix;
break;
}
} }
} }
} }
+10 -14
View File
@@ -1,22 +1,18 @@
pragma Singleton pragma Singleton
import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Hyprland
import qs.Helpers
Singleton { Singleton {
function getInitialTitle(callback) { function getInitialTitle(callback) {
initialTitleProc.running = true let activeWindow = Hypr.activeToplevel.title
initialTitleProc.stdout.streamFinished.connect( function() { let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString()
let cleaned = initialTitleProc.stdout.text.trim().replace(/\"/g, "") let regex = new RegExp(activeClass, "i")
callback(cleaned === "null" ? "" : cleaned)
})
}
Process { console.log("ActiveWindow", activeWindow, "ActiveClass", activeClass, "Regex", regex)
id: initialTitleProc
command: ["./scripts/initialTitle.sh"] const evalTitle = activeWindow.match(regex)
running: false callback(evalTitle)
stdout: StdioCollector {
}
} }
} }
+2 -1
View File
@@ -9,7 +9,8 @@ import qs.Components
Item { Item {
id: root id: root
implicitWidth: expanded ? 300 : 150 implicitWidth: expanded ? 300 : 150
implicitHeight: 34 anchors.top: parent.top
anchors.bottom: parent.bottom
property bool expanded: false property bool expanded: false
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
+1 -1
View File
@@ -48,7 +48,7 @@ ShapePath {
PathLine { PathLine {
relativeX: 0 relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2) relativeY: -( root.wrapper.height - root.roundingY * 2 )
} }
PathArc { PathArc {
+15 -25
View File
@@ -11,8 +11,6 @@ import qs.Daemons
RowLayout { RowLayout {
id: root id: root
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
readonly property int vPadding: 6 readonly property int vPadding: 6
required property Wrapper popouts required property Wrapper popouts
@@ -34,26 +32,22 @@ RowLayout {
if (id === "audio" && Config.barConfig.popouts.audio) { if (id === "audio" && Config.barConfig.popouts.audio) {
popouts.currentName = "audio"; popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.currentCenter = Qt.binding( () => item.mapToItem(root, itemWidth / 2, 0 ).x );
popouts.hasCurrent = true; popouts.hasCurrent = true;
} else if ( id === "resources" && Config.barConfig.popouts.resources ) { } else if ( id === "resources" && Config.barConfig.popouts.resources ) {
popouts.currentName = "resources"; popouts.currentName = "resources";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2 + 5, 0).x); popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
popouts.hasCurrent = true; popouts.hasCurrent = true;
} else if (id === "tray" && Config.barConfig.popouts.tray) { } else if ( id === "tray" && Config.barConfig.popouts.tray ) {
const index = Math.floor(((x - top - 6) / item.implicitWidth) * item.items.count); const index = Math.floor((( x - top ) / item.implicitWidth ) * item.items.count );
const trayItem = item.items.itemAt(index); const trayItem = item.items.itemAt( index );
if (trayItem) { if ( trayItem ) {
popouts.currentName = `traymenu${index}`; popouts.currentName = `traymenu${ index }`;
popouts.currentCenter = Qt.binding(() => trayItem.mapToItem(root, trayItem.implicitWidth / 2 + 4, 0).x); popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x );
popouts.hasCurrent = true; popouts.hasCurrent = true;
} else { } else {
popouts.hasCurrent = false; popouts.hasCurrent = false;
} }
} else if (id === "activeWindow" && Config.barConfig.popouts.activeWindow) {
popouts.currentName = id.toLowerCase();
popouts.currentCenter = item.mapToItem(root, 0, itemHeight / 2).y;
popouts.hasCurrent = true;
} }
} }
@@ -107,17 +101,13 @@ RowLayout {
DelegateChoice { DelegateChoice {
roleValue: "notifBell" roleValue: "notifBell"
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: NotifBell { sourceComponent: NotifBell {}
Layout.alignment: Qt.AlignHCenter
}
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "clock" roleValue: "clock"
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: Clock { sourceComponent: Clock {}
Layout.alignment: Qt.AlignHCenter
}
} }
} }
DelegateChoice { DelegateChoice {
@@ -134,6 +124,9 @@ RowLayout {
required property string id required property string id
required property int index required property int index
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
function findFirstEnabled(): Item { function findFirstEnabled(): Item {
const count = repeater.count; const count = repeater.count;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -153,11 +146,8 @@ RowLayout {
return null; return null;
} }
Layout.alignment: Qt.AlignHCenter Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0
// Cursed ahh thing to add padding to first and last enabled components
Layout.topMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.bottomMargin: findLastEnabled() === this ? root.vPadding : 0
visible: enabled visible: enabled
active: enabled active: enabled
+8 -1
View File
@@ -4,9 +4,16 @@ import qs.Modules
Item { Item {
implicitWidth: timeText.contentWidth implicitWidth: timeText.contentWidth
implicitHeight: timeText.contentHeight anchors.top: parent.top
anchors.bottom: parent.bottom
Text { Text {
id: timeText id: timeText
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: Time.time text: Time.time
color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white" color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white"
+3 -7
View File
@@ -12,16 +12,13 @@ Item {
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
readonly property Item current: currentPopout?.item ?? null readonly property Item current: currentPopout?.item ?? null
anchors.centerIn: parent implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 10 * 2
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 10 * 2
Item { Item {
id: content id: content
anchors.fill: parent anchors.fill: parent
anchors.margins: 10
Popout { Popout {
name: "audio" name: "audio"
@@ -80,8 +77,7 @@ Item {
required property string name required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name readonly property bool shouldBeActive: root.wrapper.currentName === name
anchors.top: parent.top anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
opacity: 0 opacity: 0
scale: 0.8 scale: 0.8
+11 -1
View File
@@ -4,11 +4,21 @@ import qs.Helpers
import qs.Components import qs.Components
Item { Item {
id: root
implicitWidth: 20 implicitWidth: 20
implicitHeight: 18 anchors.top: parent.top
anchors.bottom: parent.bottom
MaterialIcon { MaterialIcon {
id: notificationCenterIcon id: notificationCenterIcon
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white" property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white"
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4" text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pixelSize: 20 font.pixelSize: 20
+2
View File
@@ -14,8 +14,10 @@ Item {
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 34 implicitHeight: 34
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
clip: true
Rectangle { Rectangle {
id: backgroundRect
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
+6
View File
@@ -7,9 +7,15 @@ import Quickshell.Services.SystemTray
Row { Row {
id: root id: root
anchors.top: parent.top
anchors.bottom: parent.bottom
required property PanelWindow bar required property PanelWindow bar
readonly property alias items: repeater readonly property alias items: repeater
spacing: 0 spacing: 0
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items
+7 -3
View File
@@ -6,12 +6,16 @@ import qs.Config
Item { Item {
id: root id: root
property int countUpdates: Updates.availableUpdates property int countUpdates: Updates.availableUpdates
implicitWidth: contentRow.childrenRect.width + 10 implicitWidth: textMetrics.width + contentRow.spacing + 30
implicitHeight: 22 anchors.top: parent.top
anchors.bottom: parent.bottom
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
Rectangle { Rectangle {
anchors.fill: parent anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: 22
radius: height / 2 radius: height / 2
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000" color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000"
Behavior on color { Behavior on color {
+13 -21
View File
@@ -3,10 +3,11 @@ import QtQuick.Layouts
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
import qs.Components
Item { Item {
id: root id: root
property string currentTitle property string currentTitle: Hypr.activeName
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10 Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10
clip: true clip: true
@@ -14,15 +15,15 @@ Item {
property bool showFirst: true property bool showFirst: true
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white"
Component.onCompleted: { // Component.onCompleted: {
Hyprland.rawEvent.connect(( event ) => { // Hyprland.rawEvent.connect(( event ) => {
if (event.name === "activewindow") { // if (event.name === "activewindow") {
InitialTitle.getInitialTitle( function( initialTitle ) { // InitialTitle.getInitialTitle( function( initialTitle ) {
root.currentTitle = initialTitle // root.currentTitle = initialTitle
}) // })
} // }
}) // })
} // }
onCurrentTitleChanged: { onCurrentTitleChanged: {
if (showFirst) { if (showFirst) {
@@ -34,14 +35,13 @@ Item {
} }
} }
Text { CustomText {
id: titleText1 id: titleText1
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
text: root.currentTitle text: root.currentTitle
color: root.textColor color: root.textColor
elide: Text.ElideRight elide: Text.ElideRight
font.family: "Rubik"
font.pixelSize: 16 font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -49,13 +49,9 @@ Item {
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
} }
Behavior on color {
CAnim {}
}
} }
Text { CustomText {
id: titleText2 id: titleText2
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
@@ -68,9 +64,5 @@ Item {
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
} }
Behavior on color {
CAnim {}
}
} }
} }
+6 -3
View File
@@ -12,14 +12,17 @@ import qs.Components
Item { Item {
id: itemRoot id: itemRoot
required property PanelWindow bar required property PanelWindow bar
implicitHeight: 28 anchors.top: parent.top
implicitWidth: root.implicitWidth anchors.bottom: parent.bottom
implicitWidth: workspacesRow.implicitWidth + 10
Rectangle { Rectangle {
id: root id: root
property HyprlandMonitor monitor: Hyprland.monitorFor( itemRoot.bar?.screen ) property HyprlandMonitor monitor: Hyprland.monitorFor( itemRoot.bar?.screen )
implicitWidth: workspacesRow.implicitWidth + 10 anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: 22 implicitHeight: 22
function shouldShow(monitor) { function shouldShow(monitor) {
-2
View File
@@ -71,8 +71,6 @@ Item {
shouldBeActive: root.hasCurrent shouldBeActive: root.hasCurrent
asynchronous: true asynchronous: true
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content { sourceComponent: Content {
wrapper: root wrapper: root
+70 -6
View File
@@ -1,14 +1,78 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import Quickshell.Hyprland
FloatingWindow { FloatingWindow {
id: root
title: "terminal" title: "terminal"
minimumSize: Qt.size(400, 300)
Component.onCompleted: { Rectangle {
Hyprland.refreshToplevels() id: root
console.log(Hyprland.toplevels.values) width: 480
height: 320
property int callsToUpdateMinimumWidth: 0
property bool optimize: true
property int currentTextModel: 0
property var columnTexts: [
["Click on either", "rectangle above", "and note how the counter", "below updates", "significantly faster using the", "regular (non-optimized)", "implementation"],
["The width", "of this column", "is", "no wider than the", "widest item"],
["Note how using Qt.callLater()", "the minimum width is", "calculated a bare-minimum", "number", "of times"]
]
Text {
x: 20; y: 280
text: "Times minimum width has been calculated: " + root.callsToUpdateMinimumWidth
}
Row {
y: 25; spacing: 30; anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 200; height: 50; color: "lightgreen"
Text { text: "Optimized behavior\nusing Qt.callLater()"; anchors.centerIn: parent }
MouseArea { anchors.fill: parent; onClicked: { root.optimize = true; root.currentTextModel++ } }
}
Rectangle {
width: 200; height: 50; color: "lightblue"
Text { text: "Regular behavior"; anchors.centerIn: parent}
MouseArea { anchors.fill: parent; onClicked: { root.optimize = false; root.currentTextModel++ } }
}
}
Column {
id: column
anchors.centerIn: parent
onChildrenChanged: root.optimize ? Qt.callLater(updateMinimumWidth) : updateMinimumWidth()
property int widestChild
function updateMinimumWidth() {
root.callsToUpdateMinimumWidth++
var w = 0;
for (var i in children) {
var child = children[i];
if (child.implicitWidth > w) {
w = child.implicitWidth;
}
}
widestChild = w;
}
Repeater {
id: repeater
model: root.columnTexts[root.currentTextModel%3]
delegate: Text {
id: text
required property string modelData
required property int index
color: "white"
text: modelData
width: column.widestChild
horizontalAlignment: Text.Center
Rectangle { anchors.fill: parent; z: -1; color: text.index%2 ? "gray" : "darkgray" }
}
}
}
} }
} }