Files
nixos-config/modules/user/LucidBar.qml
T
2026-11-05 17:06:39 +03:00

1092 lines
41 KiB
QML

// Lucid bar from Sn3akyy1/lucid at bdd7f79da18efd837f79dcecbe5d2ec178abc10c (MIT).
// Kept as a separate component so the other Lucid surfaces stay optional.
import "./lucidbar"
import "./lucidedit"
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs
Variants {
id: bars
property var dock
property var toastMod: null
model: Monitors.barEverywhere ? Quickshell.screens : [""]
PanelWindow {
id: bar
// a display from Quickshell.screens with a bar on every one, or "" for the one bar
required property var modelData
// the bar that speaks for the shell: it alone turns into notification
// popups, raises the privacy toast and answers the modules' ipc
readonly property bool primary: !Monitors.barEverywhere || bar.screen === Monitors.popupScreen
// what the shell's ipc targets reach on the main bar
readonly property var clockModule: clockMod
readonly property var mprisModule: mprisMod
readonly property var workspacesModule: workspacesMod
PaletteFade {
active: bar.visible
}
// the display Settings > Displays picks, or this one's own. gated,
// because unset must leave the choice to hyprland, which null would not
Binding {
target: bar
property: "screen"
value: bar.modelData !== "" ? bar.modelData : Monitors.barPlacement
when: bar.modelData !== "" || Monitors.barPlacement !== null
}
visible: Prefs.loaded && Prefs.barEnabled && Monitors.surfacesUp
property bool laidOut: false
// stand-ins take room in edit mode, so this asks after the modules themselves
readonly property bool anyModuleShown: Prefs.barZones.some((zone) => {
return bar.zoneKeys(zone).some((key) => {
return bar.realWidthOf(key) > 0.5;
});
})
// edit mode: the module in hand, how far it sits off its slot, and the
// arrangement the bar shows while it is held
property real editT: Edit.active ? 1 : 0
// in edit mode the modules part a little, so each gap has room for its clasp
readonly property real editGap: Theme.dp(16) * bar.editT
property string dragKey: ""
property real dragShift: 0
property real dragLift: 0
property var dragGroups: null
property bool dragHeld: false
Behavior on editT {
NumberAnimation {
duration: Theme.barMs(320)
easing.type: Easing.OutCubic
}
}
function xOf(key) {
return bar.placeFor(key) + (key === bar.dragKey ? bar.dragShift : 0);
}
function liftOf(key) {
return key === bar.dragKey ? bar.dragLift : 0;
}
// a right click on a module: its sheet, on the side of the screen away from it
function editModule(key) {
const mod = bar.pillFor[key];
const cx = bar.xOf(key) + (mod ? mod.width : 0) / 2;
Edit.openSheet("module", key, Edit.sideFor(cx, bar.width), bar.screen ? bar.screen.name : "");
}
// the sheet's open button: this bar's module opens as a click would open it
Connections {
function onModuleOpenRequested(id, screen) {
if (screen !== "" ? (!bar.screen || bar.screen.name !== screen) : !bar.primary)
return ;
const mod = bar.pillFor[id];
if (!mod)
return ;
if (id === "start") {
dock.openLauncher("");
} else if (id === "media") {
mod.openPanel("player");
} else {
if (mod.compactClicked)
mod.compactClicked();
mod.expanded = true;
}
}
target: Edit
}
// keys, when given, lets modules joined to the next one sit flush against it
function placeGroup(widths, originX, keys) {
const gap = Theme.dp(Prefs.barSpacing) + bar.editGap;
const out = [];
let x = originX;
let any = false;
let trailing = 0;
for (let i = 0; i < widths.length; i++) {
out.push(x);
const w = widths[i];
if (w > 0.5) {
trailing = keys && bar.joinedAfter(keys, widths, i) ? 0 : (gap > 0 ? gap * Math.min(1, w / gap) : 0);
x += w + trailing;
any = true;
}
}
out.push(any ? Math.max(originX, x - trailing) : originX);
return out;
}
// whether the module at i is joined to the one right after it. both
// have to be in the bar this moment: one that has emptied out (a tray
// with no icons, workspaces off in the overview) parts its group there
function joinedAfter(keys, widths, i) {
return i + 1 < keys.length && widths[i] > 0.5 && widths[i + 1] > 0.5 && bar.realWidthOf(keys[i]) > 0.5 && bar.realWidthOf(keys[i + 1]) > 0.5 && Prefs.barJoinedNow(keys[i], keys[i + 1]);
}
// what a module's pill reads to square off the corners it shares
function joinedLeftOf(key) {
const at = bar.placeMap[key];
if (!at || at.at === 0)
return false;
const keys = bar.zoneKeys(at.zone);
return bar.realWidthOf(key) > 0.5 && bar.realWidthOf(keys[at.at - 1]) > 0.5 && Prefs.barJoinedNow(keys[at.at - 1], key);
}
function joinedRightOf(key) {
const at = bar.placeMap[key];
if (!at)
return false;
const keys = bar.zoneKeys(at.zone);
return at.at + 1 < keys.length && bar.realWidthOf(key) > 0.5 && bar.realWidthOf(keys[at.at + 1]) > 0.5 && Prefs.barJoinedNow(key, keys[at.at + 1]);
}
// per module key, radius caps where a hanging panel meets the islands: {corners, tops, sides, fillets}
readonly property var hangPlan: bar.planHangs()
function surfaceSpan(mod) {
const l = mod.x + (mod.surfaceX !== undefined ? mod.surfaceX : 0);
return [l, l + (mod.surfaceWidth !== undefined ? mod.surfaceWidth : mod.width)];
}
function planHangs() {
const plan = ({
"corners": ({}),
"tops": ({}),
"sides": ({}),
"fillets": []
});
const popups = Prefs.barPopupMode;
if (popups && !Prefs.barPopupNotched)
return plan;
const most = Theme.dp(Prefs.barNotchFlare);
const bite = 0.5;
const cap = (key, corner, v) => {
const c = plan.corners[key] || (plan.corners[key] = ({}));
c[corner] = Math.min(c[corner] === undefined ? 1e6 : c[corner], Math.max(0, v));
};
const fillet = (x, y, size, mirrored, flipped) => {
if (size >= 1)
plan.fillets.push({ "x": x, "y": y, "size": size, "mirrored": mirrored, "flipped": flipped });
};
// islands: runs of joined modules as they stand
const islands = [];
const shelves = [];
for (const zone of Prefs.barZones) {
const keys = bar.zoneKeys(zone);
let run = null;
for (let i = 0; i < keys.length; i++) {
const mod = bar.pillFor[keys[i]];
if (!mod || bar.widthOf(keys[i]) <= 0.5) {
run = null;
continue;
}
if (run && Prefs.barJoinedNow(keys[i - 1], keys[i])) {
run.keys.push(keys[i]);
run.r = bar.surfaceSpan(mod)[1];
} else {
run = ({ "keys": [keys[i]], "l": bar.surfaceSpan(mod)[0], "r": bar.surfaceSpan(mod)[1], "top": mod.y, "bottom": mod.y + mod.compactHeight });
islands.push(run);
}
}
}
// what hangs: a notched pop-up, or a joined module opened in place
for (const isle of islands) {
for (let j = 0; j < isle.keys.length; j++) {
const m = bar.pillFor[isle.keys[j]];
if (popups) {
const pop = m.notchedPopup === true && m.popupOpen ? m.popupItem : null;
if (pop && pop.height > 0.5)
shelves.push({ "key": isle.keys[j], "l": m.x + pop.x, "r": m.x + pop.x + pop.width, "y": m.y + pop.y, "depth": pop.height });
} else if (isle.keys.length > 1 && m !== workspacesMod && m.height - m.compactHeight > 0.5) {
const depth = m.height - m.compactHeight;
const side = ({});
if (j > 0) {
const run = m.x - isle.l;
const f = Math.floor(Math.min(most, run / 2, depth / 2));
fillet(m.x - f + bite, isle.bottom - bite, f, false, false);
cap(isle.keys[0], "bl", run - f);
side.bl = depth - f;
}
if (j < isle.keys.length - 1) {
const run = isle.r - (m.x + m.width);
const f = Math.floor(Math.min(most, run / 2, depth / 2));
fillet(m.x + m.width - bite, isle.bottom - bite, f, true, false);
cap(isle.keys[isle.keys.length - 1], "br", run - f);
side.br = depth - f;
}
plan.sides[isle.keys[j]] = side;
}
}
}
// a pop-up's top is the screen edge upside down: islands stand on it or it hangs from them
for (const shelf of shelves) {
const over = islands.filter((isle) => {
return isle.l < shelf.r - 0.5 && isle.r > shelf.l + 0.5 && Math.abs(isle.bottom - shelf.y) < 1;
});
const top = ({});
plan.tops[shelf.key] = top;
if (over.length === 0)
continue;
for (let k = 0; k < over.length; k++) {
const isle = over[k];
const first = isle.keys[0];
const last = isle.keys[isle.keys.length - 1];
const tall = isle.bottom - isle.top;
const dl = isle.l - shelf.l;
if (k > 0) {
const f = Math.floor(Math.min(most, (isle.l - over[k - 1].r) / 2, tall));
fillet(isle.l - f + bite, shelf.y - f + bite, f, false, true);
cap(first, "bl", 0);
} else if (dl > 0.5) {
const f = Math.floor(Math.min(most, dl / 2, tall));
fillet(isle.l - f + bite, shelf.y - f + bite, f, false, true);
cap(first, "bl", 0);
top.tl = dl - f;
} else if (dl < -0.5) {
const f = Math.floor(Math.min(most, -dl / 2, shelf.depth / 2));
fillet(shelf.l - f + bite, shelf.y - bite, f, false, false);
cap(first, "bl", -dl - f);
top.tl = 0;
} else {
cap(first, "bl", 0);
top.tl = 0;
}
const dr = shelf.r - isle.r;
if (k < over.length - 1) {
const f = Math.floor(Math.min(most, (over[k + 1].l - isle.r) / 2, tall));
fillet(isle.r - bite, shelf.y - f + bite, f, true, true);
cap(last, "br", 0);
} else if (dr > 0.5) {
const f = Math.floor(Math.min(most, dr / 2, tall));
fillet(isle.r - bite, shelf.y - f + bite, f, true, true);
cap(last, "br", 0);
top.tr = dr - f;
} else if (dr < -0.5) {
const f = Math.floor(Math.min(most, -dr / 2, shelf.depth / 2));
fillet(shelf.r - bite, shelf.y - bite, f, true, false);
cap(last, "br", -dr - f);
top.tr = 0;
} else {
cap(last, "br", 0);
top.tr = 0;
}
}
}
return plan;
}
property real wsCollapse: (workspacesMod.expanded && !Prefs.barPopupMode) ? 0 : 1
// the modules are addressed by their id in Prefs.barModules; the
// groups in Prefs.barLayout decide which slot each one lands in
readonly property var pillFor: ({
"workspaces": workspacesMod,
"media": mprisMod,
"tray": sysTrayMod,
"clock": clockMod,
"notifications": notifMod,
"system": systemMod,
"privacy": privacyMod,
"power": powerMod,
"window": windowMod,
"start": startMod
})
// what BarPill looks itself up in, for a right click to its card
readonly property var moduleById: bar.pillFor
readonly property var modules: [workspacesMod, mprisMod, sysTrayMod, clockMod, notifMod, systemMod, privacyMod, powerMod, windowMod, startMod]
// which modules switched on have left the bar for now, for the Bar
// page's arrangement
Binding {
target: Prefs
property: "barModulesAway"
when: bar.primary
value: Prefs.barModules.filter((m) => {
const mod = bar.pillFor[m.id];
return Prefs[m.key] === true && mod && !mod.shown;
}).map((m) => {
return m.id;
})
}
// a module on its way out of the bar keeps the slot it had until its
// pill has finished emptying, so its neighbours close the gap at the
// rate it shrinks rather than dropping into it
function zoneKeys(zone) {
const live = bar.dragGroups ? bar.dragGroups[zone === "centre" ? "center" : zone].filter((id) => {
return Prefs.barShown(id);
}) : Prefs.barKeysOf(zone);
const r = Prefs.barRetiring;
if (!r || r.zone !== zone || Prefs.barHas(r.key))
return live;
const out = live.slice();
out.splice(Math.min(r.at, out.length), 0, r.key);
return out;
}
readonly property var retiring: Prefs.barRetiring
onRetiringChanged: {
if (bar.retiring)
retireTimer.restart();
}
Timer {
id: retireTimer
interval: Theme.barMs(560)
onTriggered: Prefs.barClearRetiring()
}
// key -> which zone it sits in and how far along
readonly property var placeMap: {
const out = ({});
for (var z = 0; z < Prefs.barZones.length; z++) {
const zone = Prefs.barZones[z];
const keys = bar.zoneKeys(zone);
for (var i = 0; i < keys.length; i++) out[keys[i]] = ({ "zone": zone, "at": i })
}
return out;
}
// workspaces empties its slot as it flies to the screen centre, so the
// rest of its zone closes up behind it
function realWidthOf(key) {
const it = bar.pillFor[key];
if (!it)
return 0;
return it === workspacesMod ? it.width * bar.wsCollapse : it.width;
}
// in edit mode a module with nothing to show keeps a stand-in's room
function widthOf(key) {
return Math.max(bar.realWidthOf(key), barEdit.ghostWidthOf(key));
}
function widthsOf(zone) {
return bar.zoneKeys(zone).map(bar.widthOf);
}
function placesOf(zone) {
return zone === "left" ? bar.leftPlaces : (zone === "centre" ? bar.centrePlaces : bar.rightPlaces);
}
function alignFor(zone) {
return zone === "centre" ? "center" : (zone === "right" ? "right" : "left");
}
function placeFor(key) {
const at = bar.placeMap[key];
if (!at)
return 0;
const places = bar.placesOf(at.zone);
return at.at < places.length ? places[at.at] : 0;
}
function alignOf(key) {
const at = bar.placeMap[key];
return bar.alignFor(at ? at.zone : "left");
}
readonly property var leftWidths: bar.widthsOf("left")
readonly property var centreWidths: bar.widthsOf("centre")
readonly property var rightWidths: bar.widthsOf("right")
readonly property real leftGroupWidth: bar.placeGroup(bar.leftWidths, 0, bar.zoneKeys("left"))[bar.leftWidths.length]
readonly property real centreGroupWidth: bar.placeGroup(bar.centreWidths, 0, bar.zoneKeys("centre"))[bar.centreWidths.length]
readonly property real rightGroupWidth: bar.placeGroup(bar.rightWidths, 0, bar.zoneKeys("right"))[bar.rightWidths.length]
readonly property int zoneGap: Theme.dp(Prefs.barZoneGap)
// pills ease to their new slots only while the arrangement is changing.
// a Behavior left on all the time would sit on top of the width
// animations a panel opening or a tray icon arriving already drive, and
// one that restarts every frame never arrives
readonly property int reorderTick: Prefs.barReorderTick
property bool shuffling: false
onReorderTickChanged: {
bar.shuffling = true;
shuffleTimer.restart();
}
Timer {
id: shuffleTimer
interval: Theme.barMs(520)
onTriggered: bar.shuffling = false
}
readonly property real leftOriginX: bar.sideMargin
readonly property real rightOriginX: bar.width - bar.rightGroupWidth - bar.sideMargin
readonly property real centreOriginX: Math.min(Math.max((bar.width - bar.centreGroupWidth) / 2, bar.leftOriginX + bar.leftGroupWidth + bar.zoneGap), bar.rightOriginX - bar.centreGroupWidth - bar.zoneGap)
readonly property var leftPlaces: bar.placeGroup(bar.leftWidths, bar.leftOriginX, bar.zoneKeys("left"))
readonly property var centrePlaces: bar.placeGroup(bar.centreWidths, bar.centreOriginX, bar.zoneKeys("centre"))
readonly property var rightPlaces: bar.placeGroup(bar.rightWidths, bar.rightOriginX, bar.zoneKeys("right"))
Behavior on wsCollapse {
NumberAnimation {
duration: Theme.barMs(380)
easing.type: Easing.OutCubic
}
}
readonly property real sideMargin: Theme.dp(Prefs.barSideMargin)
// busy: a panel is open somewhere on the bar, so it stays out
// regardless of the pointer. held: the pointer is on the reveal
// strip or already on a module - either keeps it from hiding
readonly property bool barBusy: bar.modules.some((m) => {
return m.expanded === true || m.anyOpen === true;
})
property bool slidingAway: false
readonly property bool heldByPointer: Prefs.barAutoHide && (revealArea.containsMouse || (!bar.slidingAway && bar.modules.some((m) => {
return m.visible && (m.surfaceHovered === true || m.compactHovered === true);
})))
readonly property bool barRevealed: !Prefs.barAutoHide || bar.barBusy || bar.heldByPointer || Edit.active
// how far the compact content sits above its resting y=0 while
// hidden - past the window's own top edge, so it clips away for free
property real hiddenOffset: bar.barRevealed ? 0 : -(Theme.dp(Prefs.barHeight) + Prefs.effectiveBarTopMargin + Theme.dp(4))
Behavior on hiddenOffset {
NumberAnimation {
duration: Theme.durLong
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
onBarRevealedChanged: {
if (bar.barRevealed) {
bar.slidingAway = false;
slideAwayTimer.stop();
} else {
bar.slidingAway = true;
slideAwayTimer.restart();
}
}
Timer {
id: slideAwayTimer
interval: Theme.durLong
onTriggered: bar.slidingAway = false
}
Component.onCompleted: laidOutTimer.start()
Timer {
id: laidOutTimer
interval: 120
onTriggered: bar.laidOut = true
}
color: "transparent"
implicitHeight: bar.screen ? bar.screen.height - Prefs.effectiveBarTopMargin : Theme.dp(800)
exclusiveZone: (Prefs.barEnabled && bar.anyModuleShown && !Prefs.barAutoHide) ? Theme.dp(Prefs.barHeight) + Prefs.shellGap : 0
anchors {
top: true
bottom: false
left: true
right: true
}
margins {
top: Prefs.effectiveBarTopMargin
}
// full bar: one strip across the whole top edge, under every module
Rectangle {
id: fullStrip
visible: Prefs.barFull && bar.anyModuleShown
x: 0
y: bar.hiddenOffset
z: -2
width: bar.width
height: Theme.dp(Prefs.barHeight)
color: Theme.bg
Behavior on color {
enabled: bar.laidOut
ColorAnimation {
duration: Theme.barMs(260)
easing.type: Easing.OutCubic
}
}
}
// rounded corners hanging off the strip where it meets the screen sides
BarFlare {
visible: fullStrip.visible && size > 0
mirrored: true
size: Theme.dp(Prefs.barFullCorner)
x: 0
y: Theme.dp(Prefs.barHeight) + bar.hiddenOffset
z: -2
}
BarFlare {
visible: fullStrip.visible && size > 0
size: Theme.dp(Prefs.barFullCorner)
x: bar.width - width
y: Theme.dp(Prefs.barHeight) + bar.hiddenOffset
z: -2
}
// full bar: an open panel hangs off the strip, so round the two concave
// corners where its sides meet the strip's lower edge. They grow with
// the panel's height, so a folding panel takes them with it
Repeater {
model: (Prefs.barFull && !Prefs.barPopupMode) ? bar.modules.filter((m) => {
return m !== workspacesMod;
}) : []
Item {
id: panelFlares
required property var modelData
// surfaceOpen, not the height alone: a pill that is only
// resizing its compact face is see-through on the full bar
readonly property real reach: (panelFlares.modelData.visible && panelFlares.modelData.surfaceOpen === true) ? panelFlares.modelData.height - Theme.dp(Prefs.barHeight) : 0
// never past the screen edge, and never into the screen corner
// that already rounds that side
function sizeFor(toTheLeft) {
var m = panelFlares.modelData;
var corner = Theme.dp(Prefs.barFullCorner);
var toEdge = toTheLeft ? m.x : bar.width - m.x - m.width;
var room = toEdge - (toEdge < corner * 2 ? corner : 0);
return Math.max(0, Math.floor(Math.min(corner, panelFlares.reach, room)));
}
anchors.fill: parent
z: -1
BarFlare {
size: panelFlares.sizeFor(true)
x: panelFlares.modelData.x - width + 0.5
y: Theme.dp(Prefs.barHeight)
}
BarFlare {
mirrored: true
size: panelFlares.sizeFor(false)
x: panelFlares.modelData.x + panelFlares.modelData.width - 0.5
y: Theme.dp(Prefs.barHeight)
}
}
}
Mpris {
id: mprisMod
popupAlign: bar.alignOf("media")
hostWindow: bar
x: bar.xOf("media")
editLifted: bar.dragKey === "media"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("media")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "media"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
SysTray {
id: sysTrayMod
popupAlign: bar.alignOf("tray")
hostWindow: bar
x: bar.xOf("tray")
editLifted: bar.dragKey === "tray"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("tray")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "tray"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
Clock {
id: clockMod
popupAlign: bar.alignOf("clock")
hostWindow: bar
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("clock")
x: bar.xOf("clock")
editLifted: bar.dragKey === "clock"
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "clock"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
Notifications {
id: notifMod
showsPopups: bar.primary
popupAlign: bar.alignOf("notifications")
hostWindow: bar
x: bar.xOf("notifications")
editLifted: bar.dragKey === "notifications"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("notifications")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "notifications"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
Privacy {
id: privacyMod
popupAlign: bar.alignOf("privacy")
toast: bar.primary ? toastMod : null
hostWindow: bar
x: bar.xOf("privacy")
editLifted: bar.dragKey === "privacy"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("privacy")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "privacy"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
SessionMenu {
id: powerMod
popupAlign: bar.alignOf("power")
hostWindow: bar
x: bar.xOf("power")
editLifted: bar.dragKey === "power"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("power")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "power"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
StartButton {
id: startMod
popupAlign: bar.alignOf("start")
hostWindow: bar
dockMod: dock
x: bar.xOf("start")
editLifted: bar.dragKey === "start"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("start")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "start"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
ActiveWindow {
id: windowMod
popupAlign: bar.alignOf("window")
hostWindow: bar
x: bar.xOf("window")
editLifted: bar.dragKey === "window"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("window")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "window"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
System {
id: systemMod
popupAlign: bar.alignOf("system")
hostWindow: bar
mprisMod: mprisMod
x: bar.xOf("system")
editLifted: bar.dragKey === "system"
anchors.top: parent.top
anchors.topMargin: bar.hiddenOffset + bar.liftOf("system")
Behavior on x {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "system"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
Repeater {
model: Prefs.barNotch ? bar.modules : []
Item {
id: flares
required property var modelData
readonly property bool present: flares.modelData && flares.modelData.width > 0.5 && flares.modelData.visible && !(bar.dragLift > 0.5 && bar.pillFor[bar.dragKey] === flares.modelData)
readonly property bool modHovered: flares.modelData ? (flares.modelData.compactHovered === true && flares.modelData !== workspacesMod) : false
function flareFor(toTheLeft) {
if (!flares.present)
return 0;
var mods = bar.modules;
var edge = toTheLeft ? flares.modelData.x : flares.modelData.x + flares.modelData.width;
var toEdge = toTheLeft ? edge : bar.width - edge;
var toNeighbour = 100000;
for (var i = 0; i < mods.length; i++) {
var o = mods[i];
if (!o || o === flares.modelData || o.width <= 0.5 || !o.visible)
continue;
var d = toTheLeft ? edge - (o.x + o.width) : o.x - edge;
if (d >= 0)
toNeighbour = Math.min(toNeighbour, d);
}
return Math.max(0, Math.min(Theme.dp(Prefs.barNotchFlare), Math.floor(toNeighbour / 2), Math.floor(toEdge)));
}
anchors.fill: parent
z: -1
visible: Prefs.barNotch && flares.present
readonly property real bite: 0.5
BarFlare {
hovered: flares.modHovered
size: flares.flareFor(true)
x: flares.modelData ? flares.modelData.x - width + flares.bite : 0
y: bar.hiddenOffset
}
BarFlare {
hovered: flares.modHovered
mirrored: true
size: flares.flareFor(false)
x: flares.modelData ? flares.modelData.x + flares.modelData.width - flares.bite : 0
y: bar.hiddenOffset
}
}
}
// fillets where a hanging panel meets the islands above it
Repeater {
model: 16
BarFlare {
required property int index
readonly property var spec: bar.hangPlan.fillets.length > index ? bar.hangPlan.fillets[index] : null
z: 0
size: spec ? spec.size : 0
mirrored: spec ? spec.mirrored : false
flipped: spec ? spec.flipped : false
x: spec ? spec.x : 0
y: spec ? spec.y : 0
}
}
Workspaces {
id: workspacesMod
hostWindow: bar
dockMod: dock
restX: bar.xOf("workspaces")
restY: bar.hiddenOffset + bar.liftOf("workspaces")
z: bar.dragKey === "workspaces" ? 120 : (workspacesMod.popupOpen ? 100 : 1)
Behavior on restX {
enabled: bar.laidOut && bar.shuffling && bar.dragKey !== "workspaces"
NumberAnimation {
duration: Theme.barDurEnter
easing.type: Easing.Bezier
easing.bezierCurve: Theme.easeEmphasizedDecel
}
}
}
mask: Region {
ModuleRegion {
mod: workspacesMod
}
ModuleRegion {
mod: mprisMod
}
ModuleRegion {
mod: sysTrayMod
}
ModuleRegion {
mod: clockMod
}
ModuleRegion {
mod: notifMod
}
ModuleRegion {
mod: systemMod
}
ModuleRegion {
mod: privacyMod
}
ModuleRegion {
mod: powerMod
}
ModuleRegion {
mod: windowMod
}
ModuleRegion {
mod: startMod
}
Region {
item: Prefs.barAutoHide ? revealArea : null
}
Region {
width: bar.dragHeld || Edit.active ? bar.width : 0
height: bar.dragHeld ? bar.height : (Edit.active ? Theme.dp(Prefs.barHeight) + Theme.dp(4) : 0)
}
}
BarEdit {
id: barEdit
host: bar
}
// touching this strip reveals the bar
MouseArea {
id: revealArea
x: 0
y: 0
width: bar.width
height: Theme.dp(4)
hoverEnabled: true
acceptedButtons: Qt.NoButton
enabled: Prefs.barAutoHide
visible: Prefs.barAutoHide
}
// the overview and the dock menu shut each other
Connections {
function onExpandedChanged() {
if (workspacesMod.expanded)
dock.menuOpen = false;
}
target: workspacesMod
}
Connections {
function onMenuOpenChanged() {
if (dock.menuOpen)
workspacesMod.expanded = false;
}
target: dock
}
BackgroundEffect.blurRegion: (Theme.blurAmount > 0 && bar.laidOut) ? barBlurRegion : null
// clipped to the surface: hyprland frosts the whole layer for a region
// wholly outside it, which is where a hidden bar's pills are
Region {
id: barBlurRegion
intersection: Intersection.Intersect
Region {
x: 0
y: fullStrip.visible ? bar.hiddenOffset : 0
width: fullStrip.visible ? Math.round(bar.width) : 0
height: fullStrip.visible ? Theme.dp(Prefs.barHeight) : 0
}
ModuleRegion {
blur: true
mod: workspacesMod
}
ModuleRegion {
blur: true
mod: mprisMod
}
ModuleRegion {
blur: true
mod: sysTrayMod
}
ModuleRegion {
blur: true
mod: clockMod
}
ModuleRegion {
blur: true
mod: notifMod
}
ModuleRegion {
blur: true
mod: systemMod
}
ModuleRegion {
blur: true
mod: privacyMod
}
ModuleRegion {
blur: true
mod: powerMod
}
ModuleRegion {
blur: true
mod: windowMod
}
ModuleRegion {
blur: true
mod: startMod
}
}
}
}