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>
This commit is contained in:
28allday 2026-06-13 23:26:55 +01:00
parent 152923f5d8
commit b1e2bb3bc6
12 changed files with 131 additions and 117 deletions

View file

@ -10,14 +10,18 @@ import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
ColumnLayout { // NoSignal: horizontal top bar. The widgets flow left->right in a RowLayout and
// the bar's "thickness" is its HEIGHT; hit-testing / scroll / popout anchoring all
// key off the x-axis (the position along the bar). Upstream caelestia is a
// vertical left-edge bar this is the NoSignal layout fork (Phase 3).
RowLayout {
id: root id: root
required property ShellScreen screen required property ShellScreen screen
required property DrawerVisibilities visibilities required property DrawerVisibilities visibilities
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
required property bool fullscreen required property bool fullscreen
readonly property int vPadding: Tokens.padding.large readonly property int hPadding: Tokens.padding.large
function closeTray(): void { function closeTray(): void {
if (!Config.bar.tray.compact) if (!Config.bar.tray.compact)
@ -31,8 +35,8 @@ ColumnLayout {
} }
} }
function checkPopout(y: real): void { function checkPopout(x: real): void {
const ch = childAt(width / 2, y) as WrappedLoader; const ch = childAt(x, height / 2) as WrappedLoader;
if (ch?.id !== "tray") if (ch?.id !== "tray")
closeTray(); closeTray();
@ -43,24 +47,24 @@ ColumnLayout {
} }
const id = ch.id; const id = ch.id;
const top = ch.y; const left = ch.x;
if (id === "statusIcons" && Config.bar.popouts.statusIcons) { if (id === "statusIcons" && Config.bar.popouts.statusIcons) {
const items = (ch.item as StatusIcons).items; const items = (ch.item as StatusIcons).items;
const icon = items.childAt(items.width / 2, mapToItem(items, 0, y).y); const icon = items.childAt(mapToItem(items, x, 0).x, items.height / 2);
if (icon) { if (icon) {
popouts.currentName = icon.name; popouts.currentName = icon.name;
popouts.currentCenter = Qt.binding(() => icon.mapToItem(root, 0, icon.implicitHeight / 2).y); popouts.currentCenter = Qt.binding(() => icon.mapToItem(root, icon.implicitWidth / 2, 0).x);
popouts.hasCurrent = true; popouts.hasCurrent = true;
} }
} else if (id === "tray" && Config.bar.popouts.tray) { } else if (id === "tray" && Config.bar.popouts.tray) {
const tray = ch.item as Tray; const tray = ch.item as Tray;
if (!Config.bar.tray.compact || (tray.expanded && !tray.expandIcon.contains(mapToItem(tray.expandIcon, tray.implicitWidth / 2, y)))) { if (!Config.bar.tray.compact || (tray.expanded && !tray.expandIcon.contains(mapToItem(tray.expandIcon, x, tray.implicitHeight / 2)))) {
const index = Math.floor(((y - top - tray.padding * 2 + tray.spacing) / tray.layout.implicitHeight) * tray.items.count); const index = Math.floor(((x - left - tray.padding * 2 + tray.spacing) / tray.layout.implicitWidth) * tray.items.count);
const trayItem = tray.items.itemAt(index); const trayItem = tray.items.itemAt(index);
if (trayItem) { if (trayItem) {
popouts.currentName = `traymenu${index}`; popouts.currentName = `traymenu${index}`;
popouts.currentCenter = Qt.binding(() => trayItem.mapToItem(root, 0, trayItem.implicitHeight / 2).y); 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;
@ -71,13 +75,13 @@ ColumnLayout {
} }
} else if (id === "activeWindow" && Config.bar.popouts.activeWindow && Config.bar.activeWindow.showOnHover) { } else if (id === "activeWindow" && Config.bar.popouts.activeWindow && Config.bar.activeWindow.showOnHover) {
popouts.currentName = id.toLowerCase(); popouts.currentName = id.toLowerCase();
popouts.currentCenter = (ch.item as Item).mapToItem(root, 0, (ch.item as Item).implicitHeight / 2).y ?? 0; popouts.currentCenter = (ch.item as Item).mapToItem(root, (ch.item as Item).implicitWidth / 2, 0).x ?? 0;
popouts.hasCurrent = true; popouts.hasCurrent = true;
} }
} }
function handleWheel(y: real, angleDelta: point): void { function handleWheel(x: real, angleDelta: point): void {
const ch = childAt(width / 2, y) as WrappedLoader; const ch = childAt(x, height / 2) as WrappedLoader;
if (ch?.id === "workspaces" && Config.bar.scrollActions.workspaces) { if (ch?.id === "workspaces" && Config.bar.scrollActions.workspaces) {
// Workspace scroll // Workspace scroll
const mon = (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor); const mon = (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor);
@ -86,14 +90,14 @@ ColumnLayout {
Hypr.dispatch(`togglespecialworkspace ${specialWs.slice(8)}`); Hypr.dispatch(`togglespecialworkspace ${specialWs.slice(8)}`);
else if (angleDelta.y < 0 || (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? mon.activeWorkspace?.id : Hypr.activeWsId) > 1) else if (angleDelta.y < 0 || (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? mon.activeWorkspace?.id : Hypr.activeWsId) > 1)
Hypr.dispatch(`workspace r${angleDelta.y > 0 ? "-" : "+"}1`); Hypr.dispatch(`workspace r${angleDelta.y > 0 ? "-" : "+"}1`);
} else if (y < screen.height / 2 && Config.bar.scrollActions.volume) { } else if (x < screen.width / 2 && Config.bar.scrollActions.volume) {
// Volume scroll on top half // Volume scroll on left half
if (angleDelta.y > 0) if (angleDelta.y > 0)
Audio.incrementVolume(); Audio.incrementVolume();
else if (angleDelta.y < 0) else if (angleDelta.y < 0)
Audio.decrementVolume(); Audio.decrementVolume();
} else if (Config.bar.scrollActions.brightness) { } else if (Config.bar.scrollActions.brightness) {
// Brightness scroll on bottom half // Brightness scroll on right half
const monitor = Brightness.getMonitorForScreen(screen); const monitor = Brightness.getMonitorForScreen(screen);
if (angleDelta.y > 0) if (angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + GlobalConfig.services.brightnessIncrement); monitor.setBrightness(monitor.brightness + GlobalConfig.services.brightnessIncrement);
@ -115,7 +119,7 @@ ColumnLayout {
DelegateChoice { DelegateChoice {
roleValue: "spacer" roleValue: "spacer"
delegate: WrappedLoader { delegate: WrappedLoader {
Layout.fillHeight: enabled Layout.fillWidth: enabled
} }
} }
DelegateChoice { DelegateChoice {
@ -201,11 +205,11 @@ ColumnLayout {
} }
asynchronous: true asynchronous: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
// Cursed ahh thing to add padding to first and last enabled components // Cursed ahh thing to add padding to first and last enabled components
Layout.topMargin: findFirstEnabled() === this ? root.vPadding : 0 Layout.leftMargin: findFirstEnabled() === this ? root.hPadding : 0
Layout.bottomMargin: findLastEnabled() === this ? root.vPadding : 0 Layout.rightMargin: findLastEnabled() === this ? root.hPadding : 0
visible: enabled visible: enabled
active: enabled active: enabled

View file

@ -17,10 +17,13 @@ Item {
readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name) readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name)
readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth) // 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 padding: Math.max(Tokens.padding.small, Config.border.thickness)
readonly property int contentWidth: Tokens.sizes.bar.innerWidth + padding * 2 readonly property int contentHeight: Tokens.sizes.bar.innerWidth + padding * 2
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness 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) readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
property bool isHovered property bool isHovered
@ -28,24 +31,24 @@ Item {
(content.item as Bar)?.closeTray(); (content.item as Bar)?.closeTray();
} }
function checkPopout(y: real): void { function checkPopout(x: real): void {
(content.item as Bar)?.checkPopout(y); (content.item as Bar)?.checkPopout(x);
} }
function handleWheel(y: real, angleDelta: point): void { function handleWheel(x: real, angleDelta: point): void {
(content.item as Bar)?.handleWheel(y, angleDelta); (content.item as Bar)?.handleWheel(x, angleDelta);
} }
clip: true clip: true
visible: width > Config.border.thickness visible: height > Config.border.thickness
implicitWidth: fullscreen ? 0 : Config.border.thickness implicitHeight: fullscreen ? 0 : Config.border.thickness
states: State { states: State {
name: "visible" name: "visible"
when: root.shouldBeVisible when: root.shouldBeVisible
PropertyChanges { PropertyChanges {
root.implicitWidth: root.contentWidth root.implicitHeight: root.contentHeight
} }
} }
@ -56,7 +59,7 @@ Item {
Anim { Anim {
target: root target: root
property: "implicitWidth" property: "implicitHeight"
} }
}, },
Transition { Transition {
@ -65,7 +68,7 @@ Item {
Anim { Anim {
target: root target: root
property: "implicitWidth" property: "implicitHeight"
type: Anim.Emphasized type: Anim.Emphasized
} }
} }
@ -74,14 +77,14 @@ Item {
Loader { Loader {
id: content id: content
anchors.top: parent.top anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
active: root.shouldBeVisible active: root.shouldBeVisible
sourceComponent: Bar { sourceComponent: Bar {
width: root.contentWidth height: root.contentHeight
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
popouts: root.popouts // qmllint disable incompatible-type popouts: root.popouts // qmllint disable incompatible-type

View file

@ -13,20 +13,20 @@ StyledRect {
readonly property int padding: Config.bar.clock.background ? Tokens.padding.medium : Tokens.padding.extraSmall readonly property int padding: Config.bar.clock.background ? Tokens.padding.medium : Tokens.padding.extraSmall
readonly property var font: Tokens.font.body.builders.small.scale(1.1) readonly property var font: Tokens.font.body.builders.small.scale(1.1)
implicitWidth: Tokens.sizes.bar.innerWidth implicitHeight: Tokens.sizes.bar.innerWidth
implicitHeight: layout.implicitHeight + root.padding * 2 implicitWidth: layout.implicitWidth + root.padding * 2
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0) color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.full radius: Tokens.rounding.full
ColumnLayout { RowLayout {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
Loader { Loader {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
asynchronous: true asynchronous: true
active: Config.bar.clock.showIcon active: Config.bar.clock.showIcon
visible: active visible: active
@ -38,24 +38,26 @@ StyledRect {
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
visible: Config.bar.clock.showDate visible: Config.bar.clock.showDate
horizontalAlignment: StyledText.AlignHCenter horizontalAlignment: StyledText.AlignHCenter
text: Time.format("ddd\nd") text: Time.format("ddd d")
font: Tokens.font.body.small font: Tokens.font.body.small
color: root.colour color: root.colour
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillHeight: true
Layout.topMargin: root.padding
Layout.bottomMargin: root.padding
visible: Config.bar.clock.showDate visible: Config.bar.clock.showDate
implicitHeight: 1 implicitWidth: 1
color: Colours.palette.m3outlineVariant color: Colours.palette.m3outlineVariant
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
text: Time.hourStr text: Time.hourStr
font: { font: {
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / hourMetrics.width); const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / hourMetrics.width);
@ -72,8 +74,14 @@ StyledRect {
} }
StyledText { StyledText {
Layout.topMargin: -parent.spacing - 4 Layout.alignment: Qt.AlignVCenter
Layout.alignment: Qt.AlignHCenter text: ":"
font: root.font.build()
color: root.colour
}
StyledText {
Layout.alignment: Qt.AlignVCenter
text: Time.minuteStr text: Time.minuteStr
font: { font: {
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / minMetrics.width); const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / minMetrics.width);
@ -90,8 +98,7 @@ StyledRect {
} }
Loader { Loader {
Layout.topMargin: -parent.spacing - 4 Layout.alignment: Qt.AlignVCenter
Layout.alignment: Qt.AlignHCenter
asynchronous: true asynchronous: true
active: GlobalConfig.services.useTwelveHourClock active: GlobalConfig.services.useTwelveHourClock
visible: active visible: active

View file

@ -20,16 +20,13 @@ StyledRect {
radius: Tokens.rounding.full radius: Tokens.rounding.full
clip: true clip: true
implicitWidth: Tokens.sizes.bar.innerWidth implicitHeight: Tokens.sizes.bar.innerWidth
implicitHeight: iconColumn.implicitHeight + Tokens.padding.medium * 2 - (Config.bar.status.showLockStatus && !Hypr.capsLock && !Hypr.numLock ? iconColumn.spacing : 0) implicitWidth: iconColumn.implicitWidth + Tokens.padding.medium * 2 - (Config.bar.status.showLockStatus && !Hypr.capsLock && !Hypr.numLock ? iconColumn.spacing : 0)
ColumnLayout { RowLayout {
id: iconColumn id: iconColumn
anchors.left: parent.left anchors.centerIn: parent
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: Tokens.padding.medium
spacing: Tokens.spacing.medium / 2 spacing: Tokens.spacing.medium / 2
@ -261,7 +258,7 @@ StyledRect {
required property string name required property string name
asynchronous: true asynchronous: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
visible: active visible: active
} }
} }

View file

@ -19,27 +19,27 @@ StyledRect {
property bool expanded property bool expanded
readonly property real nonAnimHeight: { readonly property real nonAnimWidth: {
if (!Config.bar.tray.compact) if (!Config.bar.tray.compact)
return layout.implicitHeight + padding * 2; return layout.implicitWidth + padding * 2;
return (expanded ? expandIcon.implicitHeight + layout.implicitHeight + spacing : expandIcon.implicitHeight) + padding * 2; return (expanded ? expandIcon.implicitWidth + layout.implicitWidth + spacing : expandIcon.implicitWidth) + padding * 2;
} }
clip: true clip: true
visible: height > 0 visible: width > 0
implicitWidth: Tokens.sizes.bar.innerWidth implicitHeight: Tokens.sizes.bar.innerWidth
implicitHeight: nonAnimHeight implicitWidth: nonAnimWidth
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, (Config.bar.tray.background && items.count > 0) ? Colours.tPalette.m3surfaceContainer.a : 0) color: Qt.alpha(Colours.tPalette.m3surfaceContainer, (Config.bar.tray.background && items.count > 0) ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.full radius: Tokens.rounding.full
Column { Row {
id: layout id: layout
anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter
anchors.top: parent.top anchors.left: parent.left
anchors.topMargin: root.padding anchors.leftMargin: root.padding
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: root.expanded || !Config.bar.tray.compact ? 1 : 0 opacity: root.expanded || !Config.bar.tray.compact ? 1 : 0
@ -86,37 +86,37 @@ StyledRect {
asynchronous: true asynchronous: true
anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter
anchors.bottom: parent.bottom anchors.right: parent.right
active: Config.bar.tray.compact && items.count > 0 active: Config.bar.tray.compact && items.count > 0
sourceComponent: Item { sourceComponent: Item {
implicitWidth: expandIconInner.implicitWidth implicitHeight: expandIconInner.implicitHeight
implicitHeight: expandIconInner.implicitHeight - Tokens.padding.small implicitWidth: expandIconInner.implicitWidth - Tokens.padding.small
MaterialIcon { MaterialIcon {
id: expandIconInner id: expandIconInner
anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter
anchors.bottom: parent.bottom anchors.right: parent.right
anchors.bottomMargin: Config.bar.tray.background ? Tokens.padding.extraSmall : -Tokens.padding.extraSmall anchors.rightMargin: Config.bar.tray.background ? Tokens.padding.extraSmall : -Tokens.padding.extraSmall
text: "expand_less" text: "expand_less"
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 270 : 90
Behavior on rotation { Behavior on rotation {
Anim {} Anim {}
} }
Behavior on anchors.bottomMargin { Behavior on anchors.rightMargin {
Anim {} Anim {}
} }
} }
} }
} }
Behavior on implicitHeight { Behavior on implicitWidth {
Anim {} Anim {}
} }
} }

View file

@ -24,7 +24,7 @@ ColumnLayout {
readonly property bool isOccupied: occupied[ws] ?? false readonly property bool isOccupied: occupied[ws] ?? false
readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: size Layout.preferredHeight: size
spacing: 0 spacing: 0

View file

@ -27,8 +27,8 @@ StyledClippingRect {
property real blur: onSpecial ? 1 : 0 property real blur: onSpecial ? 1 : 0
implicitWidth: Tokens.sizes.bar.innerWidth implicitHeight: Tokens.sizes.bar.innerWidth
implicitHeight: layout.implicitHeight + Tokens.padding.small implicitWidth: layout.implicitWidth + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
@ -60,7 +60,7 @@ StyledClippingRect {
} }
} }
ColumnLayout { RowLayout {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
@ -81,7 +81,7 @@ StyledClippingRect {
Loader { Loader {
asynchronous: true asynchronous: true
anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter
active: Config.bar.workspaces.activeIndicator active: Config.bar.workspaces.activeIndicator
sourceComponent: ActiveIndicator { sourceComponent: ActiveIndicator {

View file

@ -85,14 +85,14 @@ StyledWindow {
Region { Region {
id: emptyRegion id: emptyRegion
x: panels.notifications.x + bar.implicitWidth x: panels.notifications.x + root.borderThickness
y: panels.notifications.y + root.borderThickness y: panels.notifications.y + bar.implicitHeight
width: panels.notifications.width width: panels.notifications.width
height: panels.notifications.height height: panels.notifications.height
Region { Region {
x: root.width - width x: root.width - width
y: panels.osdWrapper.y + root.borderThickness y: panels.osdWrapper.y + bar.implicitHeight
width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness
height: panels.osd.height height: panels.osd.height
} }
@ -155,9 +155,9 @@ StyledWindow {
anchors.margins: -50 // Make border thicker to smooth out bulge from closed drawers anchors.margins: -50 // Make border thicker to smooth out bulge from closed drawers
group: blobGroup group: blobGroup
radius: root.borderRounding radius: root.borderRounding
borderLeft: bar.implicitWidth - anchors.margins - root.sdfBorderOffset borderLeft: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderTop: root.borderThickness - anchors.margins - root.sdfBorderOffset borderTop: bar.implicitHeight - anchors.margins - root.sdfBorderOffset
borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset
} }
@ -180,7 +180,7 @@ StyledWindow {
panel: panels.sessionWrapper panel: panels.sessionWrapper
deformAmount: 0.2 deformAmount: 0.2
x: panels.sessionWrapper.x + panels.session.x + bar.implicitWidth x: panels.sessionWrapper.x + panels.session.x + root.borderThickness
implicitWidth: panels.session.width implicitWidth: panels.session.width
} }
@ -199,7 +199,7 @@ StyledWindow {
panel: panels.osdWrapper panel: panels.osdWrapper
deformAmount: 0.25 deformAmount: 0.25
x: panels.osdWrapper.x + panels.osd.x + bar.implicitWidth x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
} }
@ -226,7 +226,7 @@ StyledWindow {
panel: panels.popoutsWrapper panel: panels.popoutsWrapper
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1 deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
x: panels.popoutsWrapper.x + panels.popouts.x + bar.implicitWidth - panels.popouts.width * extraWidth x: panels.popoutsWrapper.x + panels.popouts.x + root.borderThickness - panels.popouts.width * extraWidth
implicitWidth: panels.popouts.width * (1 + extraWidth) implicitWidth: panels.popouts.width * (1 + extraWidth)
Behavior on extraWidth { Behavior on extraWidth {
@ -292,8 +292,9 @@ StyledWindow {
BarWrapper { BarWrapper {
id: bar id: bar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom
screen: root.screen screen: root.screen
visibilities: visibilities visibilities: visibilities
@ -310,8 +311,8 @@ StyledWindow {
property real deformAmount: 0.15 property real deformAmount: 0.15
group: blobGroup group: blobGroup
x: panel.x + bar.implicitWidth x: panel.x + root.borderThickness
y: panel.y + root.borderThickness y: panel.y + bar.implicitHeight
implicitWidth: panel.width implicitWidth: panel.width
implicitHeight: panel.height implicitHeight: panel.height
radius: Tokens.rounding.extraLarge radius: Tokens.rounding.extraLarge

View file

@ -14,11 +14,11 @@ Scope {
ExclusionZone { ExclusionZone {
anchors.left: true anchors.left: true
exclusiveZone: root.bar.exclusiveZone
} }
ExclusionZone { ExclusionZone {
anchors.top: true anchors.top: true
exclusiveZone: root.bar.exclusiveZone
} }
ExclusionZone { ExclusionZone {

View file

@ -23,27 +23,29 @@ CustomMouseArea {
property bool osdShortcutActive property bool osdShortcutActive
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
// NoSignal top bar: the bar's thickness now insets the TOP, so panel Y-origins
// are offset by bar.implicitHeight; the left inset is just the border thickness.
function withinPanelHeight(panel: Item, x: real, y: real): bool { function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = root.borderThickness + panel.y; const panelY = root.bar.implicitHeight + panel.y;
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding; return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
} }
function withinPanelWidth(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = bar.implicitWidth + panel.x; const panelX = root.borderThickness + panel.x;
return x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding; return x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding;
} }
function inLeftPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < bar.implicitWidth + panel.x + panel.width && withinPanelHeight(panel, x, y); return x < root.borderThickness + panel.x + panel.width && withinPanelHeight(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panel.x) && withinPanelHeight(panel, x, y); return x > Math.min(width - Config.border.minThickness, root.borderThickness + panel.x) && withinPanelHeight(panel, x, y);
} }
function inTopPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); // qmllint disable missing-property const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); // qmllint disable missing-property
return y < Math.max(Config.border.minThickness, Config.border.thickness + panelHeight) && withinPanelWidth(panel, x, y); return y < Math.max(Config.border.minThickness, root.bar.implicitHeight + panelHeight) && withinPanelWidth(panel, x, y);
} }
function inBottomPanel(panel: Item, x: real, y: real, isCorner = false): bool { function inBottomPanel(panel: Item, x: real, y: real, isCorner = false): bool {
@ -54,8 +56,8 @@ CustomMouseArea {
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
if (fullscreen) if (fullscreen)
return; return;
if (event.x < bar.implicitWidth) { if (event.y < bar.implicitHeight) {
bar.handleWheel(event.y, event.angleDelta); bar.handleWheel(event.x, event.angleDelta);
} }
} }
@ -102,15 +104,15 @@ CustomMouseArea {
return; return;
} }
// Show bar in non-exclusive mode on hover // Show bar in non-exclusive mode on hover (top edge)
if (!visibilities.bar && Config.bar.showOnHover && x < bar.clampedWidth) if (!visibilities.bar && Config.bar.showOnHover && y < bar.clampedHeight)
bar.isHovered = true; bar.isHovered = true;
// Show/hide bar on drag // Show/hide bar on drag (vertical, from the top edge)
if (pressed && dragStart.x < bar.clampedWidth) { if (pressed && dragStart.y < bar.clampedHeight) {
if (dragX > Config.bar.dragThreshold) if (dragY > Config.bar.dragThreshold)
visibilities.bar = true; visibilities.bar = true;
else if (dragX < -Config.bar.dragThreshold) else if (dragY < -Config.bar.dragThreshold)
visibilities.bar = false; visibilities.bar = false;
} }
@ -128,7 +130,7 @@ CustomMouseArea {
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
const showSidebar = pressed && dragStart.x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panels.sidebar.x); const showSidebar = pressed && dragStart.x > Math.min(width - Config.border.minThickness, root.borderThickness + panels.sidebar.x);
// Show/hide session on drag // Show/hide session on drag
if (pressed && inRightPanel(panels.sessionWrapper, dragStart.x, dragStart.y) && withinPanelHeight(panels.sessionWrapper, x, y)) { if (pressed && inRightPanel(panels.sessionWrapper, dragStart.x, dragStart.y) && withinPanelHeight(panels.sessionWrapper, x, y)) {
@ -213,9 +215,9 @@ CustomMouseArea {
utilitiesShortcutActive = false; utilitiesShortcutActive = false;
} }
// Show popouts on hover // Show popouts on hover (top bar)
if (x < bar.implicitWidth) { if (y < bar.implicitHeight) {
bar.checkPopout(y); bar.checkPopout(x);
} else if ((!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) && !inLeftPanel(panels.popoutsWrapper, x, y)) { } else if ((!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) && !inLeftPanel(panels.popoutsWrapper, x, y)) {
popouts.hasCurrent = false; popouts.hasCurrent = false;
bar.closeTray(); bar.closeTray();

View file

@ -36,7 +36,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: borderThickness anchors.margins: borderThickness
anchors.leftMargin: bar.implicitWidth anchors.topMargin: bar.implicitHeight
Item { Item {
id: osdWrapper id: osdWrapper

View file

@ -15,10 +15,10 @@ Region {
readonly property real borderThickness: win.contentItem.Config.border.thickness readonly property real borderThickness: win.contentItem.Config.border.thickness
readonly property real clampedThickness: win.contentItem.Config.border.clampedThickness readonly property real clampedThickness: win.contentItem.Config.border.clampedThickness
x: bar.clampedWidth + win.dragMaskPadding x: clampedThickness + win.dragMaskPadding
y: clampedThickness + win.dragMaskPadding y: bar.clampedHeight + win.dragMaskPadding
width: win.width - bar.clampedWidth - clampedThickness - win.dragMaskPadding * 2 width: win.width - clampedThickness * 2 - win.dragMaskPadding * 2
height: win.height - clampedThickness * 2 - win.dragMaskPadding * 2 height: win.height - bar.clampedHeight - clampedThickness - win.dragMaskPadding * 2
intersection: Intersection.Xor intersection: Intersection.Xor
R { R {
@ -75,8 +75,8 @@ Region {
component R: Region { component R: Region {
required property Item panel required property Item panel
x: panel.x + root.bar.implicitWidth x: panel.x + root.borderThickness
y: panel.y + root.borderThickness y: panel.y + root.bar.implicitHeight
width: panel.width width: panel.width
height: panel.height height: panel.height
intersection: Intersection.Subtract intersection: Intersection.Subtract