nosignal-shell/modules/bar/BarWrapper.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

94 lines
2.7 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Caelestia.Config
import qs.components
import qs.utils
import qs.modules.bar.popouts as BarPopouts
Item {
id: root
required property ShellScreen screen
required property DrawerVisibilities visibilities
required property BarPopouts.Wrapper popouts
required property bool fullscreen
readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name)
// NoSignal horizontal top bar: the bar's "thickness" is its HEIGHT (was width
// for the upstream vertical left-edge bar). clampedHeight/contentHeight and
// implicitHeight carry the thickness; the wrapper fills the screen width.
readonly property int clampedHeight: Math.max(Config.border.minThickness, implicitHeight)
readonly property int padding: Math.max(Tokens.padding.small, Config.border.thickness)
readonly property int contentHeight: Tokens.sizes.bar.innerWidth + padding * 2
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentHeight : Config.border.thickness
readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
property bool isHovered
function closeTray(): void {
(content.item as Bar)?.closeTray();
}
function checkPopout(x: real): void {
(content.item as Bar)?.checkPopout(x);
}
function handleWheel(x: real, angleDelta: point): void {
(content.item as Bar)?.handleWheel(x, angleDelta);
}
clip: true
visible: height > Config.border.thickness
implicitHeight: fullscreen ? 0 : Config.border.thickness
states: State {
name: "visible"
when: root.shouldBeVisible
PropertyChanges {
root.implicitHeight: root.contentHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitHeight"
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitHeight"
type: Anim.Emphasized
}
}
]
Loader {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
active: root.shouldBeVisible
sourceComponent: Bar {
height: root.contentHeight
screen: root.screen
visibilities: root.visibilities
popouts: root.popouts // qmllint disable incompatible-type
fullscreen: root.fullscreen
}
}
}