repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Bar.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
PanelWindow {
id: root
required property Theme theme
required property Niri niriService
required property Time timeService
required property Resources resourceService
required property Notifications notificationService
required property Tray trayService
required property Media mediaService
required property Clipboard clipboardService
required property Vault vaultService
required property Launcher launcherService
required property Audio audioService
required property Brightness brightnessService
required property Network networkService
required property Bluetooth bluetoothService
required property Power powerService
required property Privilege privilegeService
required property Idle idleService
required property Lock lockService
required property Noko nokoService
required property SurfaceManager surfaces
required property Usage usageService
required property CalDav caldavService
required property Format formatService
required property string orientation
readonly property bool vertical: orientation === "vertical"
function chipLabel(name) {
switch (String(name)) {
case "clock":
return root.timeService ? root.timeService.compact : "--:--";
case "launcher":
return "launch";
case "media":
return "media";
case "noko":
return root.nokoService ? root.nokoService.label : "noko";
case "system":
return "system";
case "clipboard":
return "clip";
case "vault":
return "vault";
case "audio":
return root.audioService ? root.audioService.privacyLabel().toLowerCase() : "audio";
case "brightness":
return "bright";
case "connectivity":
return "network";
case "power":
return "power";
case "notifications":
return "notify";
case "tray":
return root.trayService && root.trayService.attentionCount > 0 ? `tray ${root.trayService.attentionCount}` : "tray";
case "idle":
return "idle";
}
return String(name);
}
// "something active inside" — drives a super subtle active dot on the chip
function chipActive(name) {
switch (String(name)) {
case "media":
return !!(root.mediaService && root.mediaService.playing);
case "noko":
return !!(root.nokoService && root.nokoService.active);
case "notifications":
return !!(root.notificationService && root.notificationService.unreadCount > 0);
case "tray":
return !!(root.trayService && root.trayService.attentionCount > 0);
case "idle":
return !!(root.idleService && root.idleService.inhibited);
case "audio":
return !!(root.audioService && root.audioService.privacyActive());
case "vault":
return !!(root.vaultService && root.vaultService.unlocked);
}
return false;
}
// idle (keep-awake) is important enough to pop louder than a subtle dot
function chipActiveStrong(name) {
const key = String(name);
if (key === "noko")
return !!(root.nokoService && root.nokoService.strong);
return key === "idle" && root.chipActive(name);
}
function chipRole(name) {
if (String(name) === "noko" && root.nokoService)
return root.nokoService.role;
return "ui.accent";
}
function itemRectInBar(item) {
let x = 0;
let y = 0;
let cursor = item;
while (cursor && cursor !== root.contentItem) {
x += cursor.x;
y += cursor.y;
cursor = cursor.parent;
}
return {
"x": x,
"y": y,
"width": item.width,
"height": item.height
};
}
function registerAnchor(name, item) {
// Skip hidden items: the horizontal clock chip and the vertical clock
// stack share the "clock" key, so only the visible one must register.
if (!root.surfaces || !item || !item.visible || item.width <= 0 || item.height <= 0)
return;
const rect = itemRectInBar(item);
root.surfaces.registerAnchor(name, `${Math.round(rect.x)},${Math.round(rect.y)},${Math.round(rect.width)},${Math.round(rect.height)}`);
}
function activatePopout(name) {
if (!root.surfaces)
return;
const target = String(name);
if (root.surfaces.popoutOpen && root.surfaces.popoutName === target) {
root.surfaces.closePopout("bar-toggle");
} else {
if (root.usageService)
root.usageService.record(target);
if (target === "vault" && root.vaultService)
root.vaultService.prepareOpen(root.niriService ? root.niriService.focusedAppId : "");
root.surfaces.openRegisteredPopout(target);
}
}
function refreshSurfaceGeometry() {
if (root.surfaces) {
const thickness = Math.round(root.vertical ? root.width : root.height);
root.surfaces.setPopoutScreen(Math.round(root.screen ? root.screen.width : root.width), Math.round(root.screen ? root.screen.height : root.height), thickness, root.orientation);
}
}
// horizontal: width spans (left+right anchored), implicitHeight sets bar
// height. vertical: height spans (top+bottom anchored), implicitWidth sets
// bar thickness from the widest chip content (never a magic constant).
implicitHeight: 34
implicitWidth: root.vertical ? (Math.max(34, widgetsRow.implicitWidth, clockStack.width, workspaceStrip.implicitWidth) + 12) : root.width
color: "transparent"
WlrLayershell.namespace: "nuguland-bar"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
Component.onCompleted: Qt.callLater(root.refreshSurfaceGeometry)
onSurfacesChanged: Qt.callLater(root.refreshSurfaceGeometry)
onWidthChanged: root.refreshSurfaceGeometry()
onHeightChanged: root.refreshSurfaceGeometry()
anchors {
top: true
left: true
right: !root.vertical
bottom: root.vertical
}
Rectangle {
id: barContent
anchors.fill: parent
color: "transparent"
// accent edge: bottom hairline (horizontal) / right hairline (vertical)
Rectangle {
x: root.vertical ? parent.width - 1 : 0
y: root.vertical ? 0 : parent.height - 1
width: root.vertical ? 1 : parent.width
height: root.vertical ? parent.height : 1
color: root.theme ? root.theme.withAlpha(root.theme.ui.accent, 0.55) : "transparent"
}
// Horizontal clock stays at the right edge; widgets sit immediately left.
// The vertical clock retains its existing centred placement.
NuguClock {
id: clockChip
stacked: false
visible: !root.vertical
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
}
NuguClock {
id: clockStack
stacked: true
visible: root.vertical
anchors.horizontalCenter: parent.horizontalCenter
y: Math.min(parent.height / 2 - height / 2, widgetsRow.y - height - 12)
}
// Workspaces fill the space before widgets horizontally, or above the
// clock vertically. Each output keeps its own faint tray.
GridLayout {
id: workspaceStrip
// horizontal base: single row from the left edge up to the widgets,
// centred vertically. Vertical state swaps to a single column from
// the top down to the clock, centred horizontally.
columns: root.vertical ? 1 : 99
rowSpacing: 8
columnSpacing: 8
clip: true
anchors.left: parent.left
anchors.leftMargin: root.vertical ? 0 : 10
anchors.right: widgetsRow.left
anchors.rightMargin: root.vertical ? 0 : 12
anchors.verticalCenter: parent.verticalCenter
anchors.topMargin: root.vertical ? 10 : 0
anchors.bottomMargin: root.vertical ? 10 : 0
states: State {
when: root.vertical
AnchorChanges {
target: workspaceStrip
anchors.left: undefined
anchors.right: undefined
anchors.verticalCenter: undefined
anchors.top: parent.top
anchors.bottom: clockStack.top
anchors.horizontalCenter: parent.horizontalCenter
}
}
Repeater {
model: root.niriService ? root.niriService.workspaceGroups() : []
// Wrap the group in a plain Item: Qt6 won't inject an array
// model's modelData into a required prop on a delegate root that
// re-declares it for a nested Repeater. The wrapper takes the
// injection and hands it to the tray/pills explicitly.
delegate: Item {
id: groupItem
required property var modelData
implicitWidth: tray.width
implicitHeight: tray.height
Layout.preferredWidth: tray.width
Layout.preferredHeight: tray.height
// pinned to the strip's start edge: left in the horizontal
// row, top in the vertical column (no centred-float gap).
Layout.alignment: root.vertical ? (Qt.AlignHCenter | Qt.AlignTop) : (Qt.AlignLeft | Qt.AlignVCenter)
Rectangle {
id: tray
anchors.centerIn: parent
width: pills.implicitWidth + 12
height: pills.implicitHeight + 12
radius: 11
color: root.theme ? root.theme.withAlpha(root.theme.ui.minor, 0.07) : "transparent"
// pills: one output's workspaces. Flow follows the bar —
// a horizontal row, or a vertical stack in vertical mode.
GridLayout {
id: pills
anchors.centerIn: parent
columns: root.vertical ? 1 : 99
rowSpacing: 4
columnSpacing: 4
Repeater {
model: groupItem.modelData.workspaces
delegate: Item {
id: pillWrap
required property var modelData
implicitWidth: workspaceChip.width
implicitHeight: workspaceChip.height
Layout.preferredWidth: workspaceChip.width
Layout.preferredHeight: workspaceChip.height
Layout.alignment: Qt.AlignCenter
Rectangle {
id: workspaceChip
readonly property bool active: !!pillWrap.modelData.is_focused
readonly property string idxStr: String(pillWrap.modelData.shortcut || pillWrap.modelData.idx || pillWrap.modelData.id || "?")
readonly property string nameStr: pillWrap.modelData.name ? String(pillWrap.modelData.name).toLowerCase() : ""
readonly property string abbrName: root.formatService && workspaceChip.nameStr ? root.formatService.abbreviate(workspaceChip.nameStr) : ""
anchors.centerIn: parent
width: Math.max(30, workspaceContent.implicitWidth + (root.vertical ? 12 : 16))
height: root.vertical ? (workspaceContent.implicitHeight + 8) : 22
radius: 8
color: root.theme ? (active ? root.theme.withAlpha(root.theme.ui.focus, 0.28) : root.theme.withAlpha(root.theme.ui.minor, 0.1)) : "transparent"
border.width: 0
// idx (minor) leads the name (normal): inline in horizontal, stacked in vertical.
GridLayout {
id: workspaceContent
anchors.centerIn: parent
columns: root.vertical ? 1 : 2
rowSpacing: 0
columnSpacing: 4
NuguText {
Layout.alignment: Qt.AlignHCenter
theme: root.theme
variant: "meta"
text: workspaceChip.idxStr
color: !root.theme ? "transparent" : (workspaceChip.active ? root.theme.ui.accent : root.theme.ui.minor)
}
NuguText {
Layout.alignment: Qt.AlignHCenter
visible: text.length > 0
theme: root.theme
variant: "meta"
font.pixelSize: root.vertical ? 10 : 13
text: root.vertical ? workspaceChip.abbrName : workspaceChip.nameStr
color: !root.theme ? "transparent" : (workspaceChip.active ? root.theme.ui.accent : root.theme.ui.normal)
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.niriService)
root.niriService.focusWorkspace(pillWrap.modelData.name || pillWrap.modelData.idx || pillWrap.modelData.id);
}
}
}
}
}
}
}
}
}
}
// Widgets keep their natural width before the clock horizontally,
// or at the bottom edge vertically.
GridLayout {
id: widgetsRow
// horizontal base: single row pinned before the clock, centred
// vertically. The vertical state swaps to a single column pinned to
// the bottom edge, centred horizontally. AnchorChanges is required
// because clearing an anchor with `undefined` does not reset it.
columns: root.vertical ? 1 : 99
rowSpacing: 4
columnSpacing: 4
anchors.right: clockChip.left
anchors.rightMargin: root.vertical ? 0 : 12
anchors.verticalCenter: parent.verticalCenter
anchors.bottomMargin: root.vertical ? 10 : 0
states: State {
when: root.vertical
AnchorChanges {
target: widgetsRow
anchors.right: undefined
anchors.verticalCenter: undefined
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
}
Repeater {
model: root.usageService ? root.usageService.layoutOrder : []
delegate: BarChip {
required property var modelData
popoutName: modelData
label: root.vertical ? root.formatService.abbreviate(root.chipLabel(modelData)) : root.chipLabel(modelData)
}
}
}
}
PopoutHost {
theme: root.theme
surfaces: root.surfaces
niriService: root.niriService
timeService: root.timeService
caldavService: root.caldavService
resourceService: root.resourceService
notificationService: root.notificationService
trayService: root.trayService
mediaService: root.mediaService
clipboardService: root.clipboardService
vaultService: root.vaultService
launcherService: root.launcherService
audioService: root.audioService
brightnessService: root.brightnessService
networkService: root.networkService
bluetoothService: root.bluetoothService
powerService: root.powerService
privilegeService: root.privilegeService
lockService: root.lockService
nokoService: root.nokoService
barWindow: root
}
component NuguText: Text {
required property Theme theme
property string variant: "body"
property bool selected: false
color: !theme ? "transparent" : (selected ? theme.content.backdrop : (variant === "title" ? theme.ui.important_global : (variant === "meta" ? theme.ui.normal : theme.content.normal)))
font.family: variant === "mono" ? "IBM Plex Mono" : "IBM Plex Sans"
font.pixelSize: variant === "title" ? 15 : (variant === "meta" ? 13 : 14)
font.bold: variant === "title"
elide: Text.ElideRight
}
component OdometerText: Item {
id: odometer
required property Theme theme
property string text: "--:--"
property bool selected: false
property int digitWidth: 10
property int digitHeight: 18
property int separatorWidth: 5
property string fontFamily: "IBM Plex Mono"
property int pixelSize: 13
readonly property string source: "Nuguland Clock.qml ClockNumber.qml"
readonly property string motion: "vertical-digit-roll"
readonly property bool noGlow: true
readonly property bool settingsBacked: false
readonly property var hardSeparators: [":"]
readonly property var cells: makeCells(text)
function makeCells(value) {
const result = [];
const chars = String(value).split("");
for (let i = 0; i < chars.length; i++) {
const ch = chars[i];
const digit = ch >= "0" && ch <= "9";
result.push(digit ? {
"kind": "digit",
"value": Number(ch)
} : {
"kind": "separator",
"value": ch
});
}
return result;
}
function colorForCell(cell) {
if (!theme)
return "white";
if (odometer.selected)
return theme.content.backdrop;
return cell.kind === "separator" ? theme.ui.accent : theme.content.normal;
}
implicitWidth: row.implicitWidth
implicitHeight: digitHeight
clip: true
Row {
id: row
anchors.centerIn: parent
spacing: 0
Repeater {
model: odometer.cells
delegate: Item {
id: cellRoot
required property var modelData
width: modelData.kind === "digit" ? odometer.digitWidth : odometer.separatorWidth
height: odometer.digitHeight
clip: true
Column {
id: digitColumn
visible: cellRoot.modelData.kind === "digit"
y: cellRoot.modelData.kind === "digit" ? -cellRoot.modelData.value * odometer.digitHeight : 0
Repeater {
model: 10
delegate: Text {
required property int index
width: odometer.digitWidth
height: odometer.digitHeight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: String(index)
font.family: odometer.fontFamily
font.pixelSize: odometer.pixelSize
color: odometer.colorForCell(cellRoot.modelData)
}
}
Behavior on y {
NumberAnimation {
duration: odometer.theme && odometer.theme.lowPowerMode ? 0 : 220
easing.type: Easing.OutCubic
}
}
}
Text {
visible: cellRoot.modelData.kind === "separator"
anchors.centerIn: parent
text: String(cellRoot.modelData.value)
font.family: odometer.fontFamily
font.pixelSize: odometer.pixelSize
color: odometer.colorForCell(cellRoot.modelData)
}
}
}
}
}
component StateLayer: MouseArea {
id: layer
property Theme theme
property bool checked: false
property bool disabled: false
property bool shapeMorph: true
property real hoverOpacity: 0.08
property real pressOpacity: 0.14
property real disabledOpacity: 0.38
property point pressOrigin: Qt.point(width / 2, height / 2)
property real rippleRadius: 0
property real cornerRadius: 0
property real endRadius: Math.sqrt(width * width + height * height) * (shapeMorph ? 1.25 : 1)
property string sourceMechanic: "Nuguland StateLayer.qml ButtonBase.qml"
signal activated
function stateLayerContract() {
return {
"clickablePrimitive": "NuguStateLayer",
"source": sourceMechanic,
"hoverOpacity": hoverOpacity,
"pressOpacity": pressOpacity,
"disabledOpacity": disabledOpacity,
"rippleRadius": "radial-press-feedback",
"pressOrigin": "pointer-local-origin",
"shapeMorph": shapeMorph,
"keyboardActivation": ["Return", "Space"],
"roles": {
"checked": "ui.focus",
"hover": "ui.accent",
"disabled": "ui.minor"
}
};
}
function beginPress(x, y) {
pressOrigin = Qt.point(x, y);
rippleRadius = 0;
rippleAnimation.restart();
}
function keyboardActivate() {
beginPress(width / 2, height / 2);
activated();
}
anchors.fill: parent
clip: true
enabled: !disabled
hoverEnabled: true
acceptedButtons: Qt.LeftButton
cursorShape: disabled ? Qt.ArrowCursor : Qt.PointingHandCursor
activeFocusOnTab: !disabled
onPressed: event => {
return beginPress(event.x, event.y);
}
onClicked: activated()
Keys.onReturnPressed: event => {
event.accepted = true;
keyboardActivate();
}
Keys.onSpacePressed: event => {
event.accepted = true;
keyboardActivate();
}
Rectangle {
anchors.fill: parent
radius: layer.cornerRadius
color: layer.theme ? layer.theme.ui.accent : "white"
opacity: layer.disabled ? layer.disabledOpacity : layer.pressed ? layer.pressOpacity : layer.containsMouse || layer.checked ? layer.hoverOpacity : 0
Behavior on opacity {
NumberAnimation {
duration: layer.theme && layer.theme.lowPowerMode ? 0 : 120
easing.type: Easing.OutCubic
}
}
}
Rectangle {
width: layer.rippleRadius * 2
height: width
x: layer.pressOrigin.x - width / 2
y: layer.pressOrigin.y - height / 2
radius: width / 2
color: layer.theme ? layer.theme.ui.accent : "white"
opacity: layer.pressed ? layer.pressOpacity : 0
Behavior on opacity {
NumberAnimation {
duration: layer.theme && layer.theme.lowPowerMode ? 0 : 160
easing.type: Easing.OutCubic
}
}
}
NumberAnimation {
id: rippleAnimation
target: layer
property: "rippleRadius"
to: layer.endRadius
duration: layer.theme && layer.theme.lowPowerMode ? 0 : 220
easing.type: Easing.OutCubic
}
}
// Shared clock renderer. `stacked` lays HH / rule / MM in a column (vertical
// bar); otherwise HH | rule | MM in a row (horizontal bar). The accent rule
// is a Rectangle, so it never depends on a font glyph.
component NuguClock: Rectangle {
id: clk
property bool stacked: false
readonly property bool selected: root.surfaces && root.surfaces.popoutName === "clock"
readonly property var clockParts: String(root.chipLabel("clock")).split(":")
readonly property string hh: clk.clockParts[0] || "--"
readonly property string mm: clk.clockParts[1] || "--"
implicitWidth: clockContent.implicitWidth + (stacked ? 12 : 16)
implicitHeight: clockContent.implicitHeight + (stacked ? 12 : 8)
width: implicitWidth
height: implicitHeight
radius: 9
color: !root.theme ? "transparent" : (selected ? root.theme.ui.focus : root.theme.withAlpha(root.theme.ui.minor, 0.1))
clip: true
Component.onCompleted: Qt.callLater(() => root.registerAnchor("clock", clk))
onXChanged: root.registerAnchor("clock", clk)
onYChanged: root.registerAnchor("clock", clk)
onWidthChanged: root.registerAnchor("clock", clk)
onHeightChanged: root.registerAnchor("clock", clk)
GridLayout {
id: clockContent
anchors.centerIn: parent
columns: clk.stacked ? 1 : 3
rowSpacing: clk.stacked ? 3 : 0
columnSpacing: 6
NuguText {
Layout.alignment: Qt.AlignCenter
theme: root.theme
variant: "meta"
text: clk.hh
color: !root.theme ? "transparent" : (clk.selected ? root.theme.content.backdrop : root.theme.content.normal)
}
Rectangle {
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: clk.stacked ? 16 : 2
Layout.preferredHeight: clk.stacked ? 2 : 14
radius: 1
color: root.theme ? root.theme.ui.accent : "transparent"
}
NuguText {
Layout.alignment: Qt.AlignCenter
theme: root.theme
variant: "meta"
text: clk.mm
color: !root.theme ? "transparent" : (clk.selected ? root.theme.content.backdrop : root.theme.content.normal)
}
}
StateLayer {
theme: root.theme
cornerRadius: clk.radius
checked: clk.selected
onActivated: {
if (root.surfaces)
root.activatePopout("clock");
}
}
}
component BarChip: Rectangle {
id: chip
required property string popoutName
required property string label
property int preferredWidth: Math.max(52, labelText.implicitWidth + 18)
readonly property bool selected: root.surfaces && root.surfaces.popoutName === popoutName
readonly property bool active: root.chipActive(popoutName)
readonly property bool activeStrong: root.chipActiveStrong(popoutName)
readonly property bool nokoCapture: popoutName === "noko" && root.nokoService && (root.nokoService.listening || root.nokoService.transcribing)
readonly property color roleColor: !root.theme ? "transparent" : (root.chipRole(popoutName) === "warning" ? root.theme.warning : root.theme.ui.accent)
Layout.preferredWidth: preferredWidth
Layout.preferredHeight: 26
Layout.alignment: Qt.AlignVCenter
radius: 9
color: !root.theme ? "transparent" : (selected ? root.theme.ui.focus : (activeStrong && !selected ? root.theme.withAlpha(chip.roleColor, 0.3) : root.theme.withAlpha(root.theme.ui.minor, 0.1)))
clip: true
border.width: chip.activeStrong && !chip.selected ? 1 : 0
border.color: root.theme && chip.activeStrong ? chip.roleColor : "transparent"
Component.onCompleted: Qt.callLater(() => {
return root.registerAnchor(chip.popoutName, chip);
})
onXChanged: root.registerAnchor(chip.popoutName, chip)
onYChanged: root.registerAnchor(chip.popoutName, chip)
onWidthChanged: root.registerAnchor(chip.popoutName, chip)
onHeightChanged: root.registerAnchor(chip.popoutName, chip)
NuguText {
id: labelText
anchors.centerIn: parent
width: Math.max(0, parent.width - 14)
theme: root.theme
variant: "meta"
selected: chip.selected
text: chip.label
font.bold: chip.activeStrong && !chip.selected
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
color: !root.theme ? "transparent" : (chip.selected ? root.theme.content.backdrop : (chip.activeStrong ? chip.roleColor : root.theme.content.normal))
z: 2
}
Rectangle {
id: activeDot
visible: chip.active && !chip.activeStrong && !chip.selected && !chip.nokoCapture
width: 5
height: 5
radius: 2.5
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: 4
anchors.rightMargin: 5
color: root.theme ? chip.roleColor : "white"
opacity: 0.9
z: 4
Behavior on opacity {
NumberAnimation {
duration: root.theme && root.theme.lowPowerMode ? 0 : 160
easing.type: Easing.OutCubic
}
}
}
Rectangle {
visible: chip.nokoCapture
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 3
width: Math.max(16, parent.width * 0.42)
height: 2
radius: 1
color: chip.roleColor
opacity: 0.65
z: 4
SequentialAnimation on opacity {
running: chip.nokoCapture && !(root.theme && root.theme.lowPowerMode)
loops: Animation.Infinite
NumberAnimation {
to: 0.25
duration: 520
easing.type: Easing.InOutSine
}
NumberAnimation {
to: 0.78
duration: 520
easing.type: Easing.InOutSine
}
}
}
StateLayer {
theme: root.theme
cornerRadius: chip.radius
checked: chip.selected
z: 3
onActivated: {
if (chip.popoutName === "idle") {
if (root.usageService)
root.usageService.record("idle");
if (root.idleService)
root.idleService.toggle();
return;
}
if (root.surfaces)
root.activatePopout(chip.popoutName);
}
}
}
}