pragma ComponentBehavior: Bound import Quickshell import QtQuick import QtQuick.Layouts import ZShell.Config import ZShell.Components import qs.Components import qs.Modules.Bar.Components import qs.Modules.Bar.Popouts import ZShell SnapLayout { id: root required property bool fullscreen required property bool horizontal required property bool isHovered required property Wrapper popouts required property ClipWrapper popoutsWrapper required property ShellScreen screen required property ScreenState screenState readonly property int vPadding: 6 function axisCenterOf(item: Item): real { const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2); return horizontal ? ZUtils.snap(c.x, item) : ZUtils.snap(c.y, item); } function checkPopout(pos: real): void { const ch = (horizontal ? childAt(pos, height / 2) : childAt(width / 2, pos)) as EntryWrapper; const id = ch?.entryId; if (!ch || id === undefined) { if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) popouts.hasCurrent = false; return; } if (screenState.sidebar || screenState.dashboard || screenState.resources || screenState.settings) return; if (id === "statusIcons" && Config.bar.popouts.statusIcons) { const items = (ch.item as StatusIcons).items; const icon = horizontal ? items.childAt(mapToItem(items, pos, 0).x, items.height / 2) : items.childAt(items.width / 2, mapToItem(items, 0, pos).y); if (icon) { popouts.currentName = icon.name; popouts.currentCenter = Qt.binding(() => axisCenterOf(icon)); popouts.hasCurrent = true; } } else if (id === "tray" && Config.bar.popouts.tray && Config.bar.tray.showOnHover) { const tray = ch.item as TrayIcons; const layout = tray.layout; const trayItem = horizontal ? layout.childAt(mapToItem(layout, pos, 0).x, layout.height / 2) : layout.childAt(tray.width / 2, mapToItem(layout, 0, pos).y); if (trayItem) { const idx = trayItem.index; popouts.currentName = `traymenu${idx}`; popouts.currentCenter = Qt.binding(() => axisCenterOf(trayItem)); popouts.hasCurrent = true; } if (!popouts.currentName.startsWith("traymenu")) popouts.hasCurrent = false; } if (id === "updates") { popouts.currentName = "updates"; popouts.currentCenter = Qt.binding(() => axisCenterOf(ch.item as Item)); popouts.hasCurrent = true; } } function entryAt(pos: real): string { const ch = (horizontal ? childAt(pos, height / 2) : childAt(width / 2, pos)) as EntryWrapper; return ch?.entryId ?? ""; } readonly property var sectionRects: { const count = repeater.count; // dependency: re-run when items are added/removed const names = ["start", "middle", "end"]; // constant ids by position const bounds = [null]; let spacers = 0; for (let i = 0; i < count; i++) { const it = repeater.itemAt(i) as EntryWrapper; if (!it) continue; if (it.entryId === "spacer") { bounds.push(null); spacers++; continue; } const l = it.x, t = it.y, r = it.x + it.width, b = it.y + it.height; const cur = bounds[spacers]; bounds[spacers] = cur ? { l: Math.min(cur.l, l), t: Math.min(cur.t, t), r: Math.max(cur.r, r), b: Math.max(cur.b, b) } : { l, t, r, b }; } return bounds.map((c, n) => ({ id: names[n] ?? `section${n}`, rect: c ? Qt.rect(c.l, c.t, c.r - c.l, c.b - c.t) : Qt.rect(0, 0, 0, 0) })); } spacing: Tokens.spacing.small orientation: horizontal ? Qt.Horizontal : Qt.Vertical Repeater { id: repeater model: ScriptModel { values: Config.bar.entries.values.filter(e => e.enabled) } DelegateChooser { role: "id" DelegateChoice { roleValue: "nightlight" delegate: EntryWrapper { NightlightWidget { horizontal: root.horizontal visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "spacer" delegate: EntryWrapper { Layout.fillHeight: !root.horizontal Layout.fillWidth: root.horizontal } } DelegateChoice { roleValue: "workspaces" delegate: EntryWrapper { Workspaces { horizontal: root.horizontal screen: root.screen visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "tray" delegate: EntryWrapper { TrayIcons { horizontal: root.horizontal loader: root popouts: root.popouts visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "statusIcons" delegate: EntryWrapper { StatusIcons { horizontal: root.horizontal visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "resources" delegate: EntryWrapper { Resources { horizontal: root.horizontal screenState: root.screenState visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "updates" delegate: EntryWrapper { UpdatesWidget { horizontal: root.horizontal visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "notifBell" delegate: EntryWrapper { NotifBell { horizontal: root.horizontal popouts: root.popouts screenState: root.screenState visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "clock" delegate: EntryWrapper { Clock { horizontal: root.horizontal loader: root popouts: root.popouts screenState: root.screenState visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "activeWindow" delegate: EntryWrapper { WindowTitle { bar: root horizontal: root.horizontal visible: !root.fullscreen || root.isHovered } } } DelegateChoice { roleValue: "network" delegate: EntryWrapper { NetworkWidget {} } } DelegateChoice { roleValue: "media" delegate: EntryWrapper { MediaWidget { horizontal: root.horizontal visible: !root.fullscreen || root.isHovered } } } } } component EntryWrapper: Item { readonly property int section: { let n = 0; for (let i = 0; i < index; i++) { if (repeater.model.values[i].id === "spacer") n++; } return n; } readonly property string entryId: modelData.id required property int index required property var modelData default property Item item Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.bottomMargin: !root.horizontal && index === repeater.count - 1 ? root.vPadding : 0 Layout.leftMargin: root.horizontal && index === 0 ? root.vPadding : (entryId === "statusIcons" && root.horizontal) ? -root.spacing + Tokens.spacing.extraSmall : 0 Layout.rightMargin: root.horizontal && index === repeater.count - 1 ? root.vPadding : 0 Layout.topMargin: !root.horizontal && index === 0 ? root.vPadding : (entryId === "statusIcons" && !root.horizontal) ? -root.spacing + Tokens.spacing.extraSmall : 0 children: item implicitHeight: item?.implicitHeight ?? 0 implicitWidth: item?.implicitWidth ?? 0 } }