nosignal-shell/modules/bar/components/workspaces/Workspaces.qml
28allday b1e2bb3bc6 feat(bar): horizontal top bar — Phase A geometry (NoSignal layout)
Convert the bar from a vertical left-edge strip to a horizontal top bar. The
shell had no orientation abstraction, so this flips the geometry across the bar
and the drawer/region/interaction layer:

- Bar.qml: ColumnLayout -> RowLayout; hit-testing (checkPopout/handleWheel),
  scroll halves and popout-center anchoring all keyed off x instead of y;
  first/last padding -> left/right margins.
- BarWrapper.qml: the bar's "thickness" is now its height (clampedHeight/
  contentHeight/implicitHeight); content anchors left/right/top.
- drawers: Exclusions (exclusive zone moved left->top), Panels (content inset
  topMargin = bar height), Regions + ContentWindow (panel->window coordinate
  mapping and the thick border flip left->top), Interactions (bar-region check
  y<barHeight, panel Y-origin uses bar height, scroll passes x, bar drag-reveal
  is vertical).
- widgets: Clock (Row, HH:MM with a colon), StatusIcons (Row), Workspaces +
  Workspace (Row), Tray (Row, expand chevron at the right) — each sizes to the
  bar height instead of width.

Verified live in the dev VM: top bar renders with centered-clock entries, the
exclusion zone keeps windows below the bar, and the dashboard drops down
correctly. Popout fly-outs still anchor sideways (Phase B).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:26:55 +01:00

150 lines
3.8 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.services
StyledClippingRect {
id: root
required property ShellScreen screen
required property bool fullscreen
readonly property bool onSpecial: (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name !== ""
readonly property int activeWsId: GlobalConfig.bar.workspaces.perMonitorWorkspaces ? (Hypr.monitorFor(screen).activeWorkspace?.id ?? 1) : Hypr.activeWsId
readonly property var occupied: {
const occ = {};
for (const ws of Hypr.workspaces.values)
occ[ws.id] = ws.lastIpcObject.windows > 0;
return occ;
}
readonly property int groupOffset: Math.floor((activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
property real blur: onSpecial ? 1 : 0
implicitHeight: Tokens.sizes.bar.innerWidth
implicitWidth: layout.implicitWidth + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.full
Item {
anchors.fill: parent
scale: root.onSpecial ? 0.8 : 1
opacity: root.onSpecial ? 0.5 : 1
visible: !root.fullscreen
layer.enabled: root.blur > 0
layer.effect: MultiEffect {
blurEnabled: true
blur: root.blur
blurMax: 32
}
Loader {
asynchronous: true
active: Config.bar.workspaces.occupiedBg
anchors.fill: parent
anchors.margins: Tokens.padding.extraSmall
sourceComponent: OccupiedBg {
workspaces: workspaces
occupied: root.occupied
groupOffset: root.groupOffset
}
}
RowLayout {
id: layout
anchors.centerIn: parent
spacing: Math.floor(Tokens.spacing.extraSmall)
Repeater {
id: workspaces
model: Config.bar.workspaces.shown
Workspace {
activeWsId: root.activeWsId
occupied: root.occupied
groupOffset: root.groupOffset
}
}
}
Loader {
asynchronous: true
anchors.verticalCenter: parent.verticalCenter
active: Config.bar.workspaces.activeIndicator
sourceComponent: ActiveIndicator {
activeWsId: root.activeWsId
workspaces: workspaces
mask: layout
fullscreen: root.fullscreen
}
}
MouseArea {
anchors.fill: layout
onClicked: event => {
const ws = (layout.childAt(event.x, event.y) as Workspace)?.ws;
if (Hypr.activeWsId !== ws)
Hypr.dispatch(`workspace ${ws}`);
else
Hypr.dispatch("togglespecialworkspace special");
}
}
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
id: specialWs
asynchronous: true
anchors.fill: parent
anchors.margins: Tokens.padding.extraSmall
active: opacity > 0
scale: root.onSpecial ? 1 : 0.5
opacity: root.onSpecial ? 1 : 0
sourceComponent: SpecialWorkspaces {
screen: root.screen
}
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Behavior on blur {
Anim {
type: Anim.StandardSmall
}
}
}