Compare commits
24 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ae8593113 | |||
| e42538896f | |||
| 2cc089b41f | |||
| 44b257ce85 | |||
| 53c5833de0 | |||
| f21e5b5eaf | |||
| 83c6b62c13 | |||
| c4738ae974 | |||
| f6a2584d31 | |||
| dcce2440f6 | |||
| 16de3f16fe | |||
| 7c06b808ae | |||
| 83d5de1ba3 | |||
| cf3c0d823d | |||
| 6a933aa260 | |||
| 36f6f5a157 | |||
| 8bada1d61a | |||
| b1e2bb3bc6 | |||
| 152923f5d8 | |||
|
|
71288e2126 | ||
|
|
f39dc4e6c7 | ||
|
|
f13126eb53 | ||
|
|
50a38546cd | ||
|
|
70e5639560 |
121 changed files with 4261 additions and 662 deletions
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
*.qml text
|
||||||
|
*.conf text
|
||||||
|
*.json text
|
||||||
BIN
assets/nosignal-logo.png
Normal file
BIN
assets/nosignal-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
20
assets/pam.d/caelestia
Normal file
20
assets/pam.d/caelestia
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#%PAM-1.0
|
||||||
|
# Caelestia lock-screen authentication (NoSignal).
|
||||||
|
#
|
||||||
|
# Deliberately does NOT use pam_faillock: a desktop screen-lock must never lock
|
||||||
|
# the user out of their OWN running session. With faillock in the path (the old
|
||||||
|
# "passwd"->system-auth route), a few failed unlocks tripped a temporary account
|
||||||
|
# lock that then refused the *correct* password until /run/faillock cleared on
|
||||||
|
# reboot. See README.md / finding F1.
|
||||||
|
#
|
||||||
|
# pam_unix verifies the password supplied via the PAM conversation (the lock UI)
|
||||||
|
# using the setuid unix_chkpwd helper, so it works for the uid-1000 Quickshell
|
||||||
|
# process. Add pam_systemd_home below if/when systemd-homed users are supported.
|
||||||
|
#
|
||||||
|
# F8a: deliberately NO `nullok` — a screen lock must never accept an empty
|
||||||
|
# password. (Only changes behaviour for empty-password accounts, which are
|
||||||
|
# correctly rejected; normal password auth is unaffected.)
|
||||||
|
auth required pam_unix.so
|
||||||
|
account required pam_unix.so
|
||||||
|
password required pam_unix.so
|
||||||
|
session required pam_unix.so
|
||||||
|
|
@ -12,10 +12,10 @@ ColumnLayout {
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
animate: true
|
animate: true
|
||||||
text: root.icon
|
icon: root.icon
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build()
|
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
components/NsIcon.qml
Normal file
32
components/NsIcon.qml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal icon: renders a Nerd Font (JetBrainsMono NF) glyph from a Material
|
||||||
|
// Symbol name via the Glyphs map. Drop-in for MaterialIcon but the input is
|
||||||
|
// `icon` (the name) instead of `text`, so dynamic bindings stay reactive.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string icon: ""
|
||||||
|
property int size: 16
|
||||||
|
// MaterialIcon API parity so NsIcon is a drop-in (these are no-ops here —
|
||||||
|
// Nerd Font has no fill/grade variable axes; sizing comes from fontStyle).
|
||||||
|
property real fill: 0
|
||||||
|
property int grade: 0
|
||||||
|
property bool animate: false
|
||||||
|
property var fontStyle: null
|
||||||
|
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: Glyphs.get(root.icon)
|
||||||
|
// "Mono" variant centers each icon glyph in a fixed cell (the plain NF
|
||||||
|
// variant uses a wide advance, which left/right-shifts centered icons).
|
||||||
|
font.family: "JetBrainsMono Nerd Font Mono"
|
||||||
|
font.pixelSize: root.fontStyle ? (root.fontStyle.pixelSize > 0 ? root.fontStyle.pixelSize : Math.round((root.fontStyle.pointSize || 12) * 1.33)) : root.size
|
||||||
|
color: Theme.text
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
43
components/NsPanel.qml
Normal file
43
components/NsPanel.qml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal floating popout container: dark-glass card, 1px border, 4px radius.
|
||||||
|
// Content goes in a ColumnLayout (default children). Sizes to its content height;
|
||||||
|
// set `width` on the instance. A background MouseArea swallows clicks so clicking
|
||||||
|
// inside the panel doesn't fall through to the overlay's close-catcher.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int pad: Theme.size.panelPad
|
||||||
|
property alias spacing: holder.spacing
|
||||||
|
default property alias content: holder.data
|
||||||
|
|
||||||
|
// anchoring hint read by NsOverlay: "center" | "right" | "trigger"
|
||||||
|
property string anchorMode: "right"
|
||||||
|
|
||||||
|
implicitHeight: holder.implicitHeight + root.pad * 2
|
||||||
|
color: Theme.panelBg
|
||||||
|
radius: Theme.radius.panel
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.panelBorder
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
// swallow clicks/hover so they don't reach the overlay close-catcher
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: holder
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: root.pad
|
||||||
|
spacing: Theme.size.gap
|
||||||
|
}
|
||||||
|
}
|
||||||
46
components/NsSwitch.qml
Normal file
46
components/NsSwitch.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal toggle switch (animated 16px knob) used in panel headers.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool on: false
|
||||||
|
signal toggled
|
||||||
|
|
||||||
|
implicitWidth: 38
|
||||||
|
implicitHeight: 22
|
||||||
|
radius: 0
|
||||||
|
color: on ? Theme.accent : Theme.fillSubtle
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.hoverMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: root.on ? parent.width - width - 3 : 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
implicitWidth: 16
|
||||||
|
implicitHeight: 16
|
||||||
|
radius: 0
|
||||||
|
color: root.on ? Theme.onAccent : Theme.text
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 120
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.toggled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,8 +45,8 @@ ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "expand_more"
|
icon: "expand_more"
|
||||||
rotation: root.expanded ? 180 : 0
|
rotation: root.expanded ? 180 : 0
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,11 @@ RowLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: upIcon
|
id: upIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "keyboard_arrow_up"
|
icon: "keyboard_arrow_up"
|
||||||
color: Colours.palette.m3onPrimary
|
color: Colours.palette.m3onPrimary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,11 +145,11 @@ RowLayout {
|
||||||
onReleased: timer.stop()
|
onReleased: timer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: downIcon
|
id: downIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "keyboard_arrow_down"
|
icon: "keyboard_arrow_down"
|
||||||
color: Colours.palette.m3onPrimary
|
color: Colours.palette.m3onPrimary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Slider {
|
||||||
|
|
||||||
background: StyledRect {
|
background: StyledRect {
|
||||||
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
||||||
radius: Tokens.rounding.full
|
radius: 0
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
@ -51,7 +51,7 @@ Slider {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
color: Colours.palette.m3inverseSurface
|
color: Colours.palette.m3inverseSurface
|
||||||
radius: Tokens.rounding.full
|
radius: 0
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: handleInteraction
|
id: handleInteraction
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import qs.services
|
||||||
ButtonBase {
|
ButtonBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias icon: label.text
|
property alias icon: label.icon
|
||||||
readonly property alias label: label
|
readonly property alias label: label
|
||||||
|
|
||||||
font: Tokens.font.icon.medium
|
font: Tokens.font.icon.medium
|
||||||
|
|
@ -34,11 +34,10 @@ ButtonBase {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenterOffset: 1 // AHHHHHHH material symbols whyyyy
|
|
||||||
color: root.onColour
|
color: root.onColour
|
||||||
fontStyle: root.font
|
fontStyle: root.font
|
||||||
fill: !root.isToggle || root.internalChecked ? 1 : 0
|
fill: !root.isToggle || root.internalChecked ? 1 : 0
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import qs.services
|
||||||
ButtonBase {
|
ButtonBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias icon: iconLabel.text
|
property string icon
|
||||||
property alias text: label.text
|
property alias text: label.text
|
||||||
|
|
||||||
readonly property alias iconLabel: iconLabel
|
readonly property alias iconLabel: iconLabel
|
||||||
|
|
@ -44,9 +44,10 @@ ButtonBase {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: iconLabel
|
id: iconLabel
|
||||||
|
|
||||||
|
icon: root.icon
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
|
||||||
color: root.onColour
|
color: root.onColour
|
||||||
|
|
|
||||||
|
|
@ -157,9 +157,9 @@ MouseArea {
|
||||||
anchors.margins: Tokens.padding.medium
|
anchors.margins: Tokens.padding.medium
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
text: item.modelData?.icon ?? ""
|
icon: item.modelData?.icon ?? ""
|
||||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,8 +176,8 @@ MouseArea {
|
||||||
active: item.modelData?.trailingIcon.length > 0
|
active: item.modelData?.trailingIcon.length > 0
|
||||||
visible: active
|
visible: active
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: item.modelData.trailingIcon
|
icon: item.modelData.trailingIcon
|
||||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,12 @@ Row {
|
||||||
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
|
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: iconLabel
|
id: iconLabel
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
animate: true
|
animate: true
|
||||||
text: root.active?.activeIcon ?? root.fallbackIcon
|
icon: root.active?.activeIcon ?? root.fallbackIcon
|
||||||
color: root.disabled ? root.disabledTextColour : root.textColour
|
color: root.disabled ? root.disabledTextColour : root.textColour
|
||||||
fill: 1
|
fill: 1
|
||||||
}
|
}
|
||||||
|
|
@ -116,13 +116,13 @@ Row {
|
||||||
onClicked: root.expanded = !root.expanded
|
onClicked: root.expanded = !root.expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: expandIcon
|
id: expandIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
|
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
|
||||||
|
|
||||||
text: "expand_more"
|
icon: "expand_more"
|
||||||
color: root.disabled ? root.disabledTextColour : root.textColour
|
color: root.disabled ? root.disabledTextColour : root.textColour
|
||||||
rotation: root.expanded ? 180 : 0
|
rotation: root.expanded ? 180 : 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,12 @@ StyledRect {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: toggleBtnIcon
|
id: toggleBtnIcon
|
||||||
|
|
||||||
visible: !!text
|
visible: !!text
|
||||||
fill: root.toggled ? 1 : 0
|
fill: root.toggled ? 1 : 0
|
||||||
text: root.icon
|
icon: root.icon
|
||||||
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
|
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
|
||||||
fontStyle: Tokens.font.icon.size(root.iconSize).build()
|
fontStyle: Tokens.font.icon.size(root.iconSize).build()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,11 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
grade: 200
|
grade: 200
|
||||||
text: image.status === Image.Error ? "broken_image" : "art_track"
|
icon: image.status === Image.Error ? "broken_image" : "art_track"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
|
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
|
||||||
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0
|
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,9 @@ Scope {
|
||||||
onPressed: {
|
onPressed: {
|
||||||
if (root.hasFullscreen)
|
if (root.hasFullscreen)
|
||||||
return;
|
return;
|
||||||
const visibilities = Visibilities.getForActive();
|
// NoSignal: drive the redesigned full-screen Power Menu (NsOverlay),
|
||||||
visibilities.session = !visibilities.session;
|
// not the old caelestia session panel (now dead code).
|
||||||
|
NsShell.toggle("power", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -17,35 +17,40 @@ 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)
|
// NoSignal redesign: the old caelestia bar is replaced by the slim NsBar
|
||||||
|
// PanelWindow. Collapse it (the drawer engine is kept for launcher/OSD/lock).
|
||||||
|
readonly property bool shouldBeVisible: false
|
||||||
property bool isHovered
|
property bool isHovered
|
||||||
|
|
||||||
function closeTray(): void {
|
function closeTray(): void {
|
||||||
(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 +61,7 @@ Item {
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
target: root
|
target: root
|
||||||
property: "implicitWidth"
|
property: "implicitHeight"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Transition {
|
Transition {
|
||||||
|
|
@ -65,7 +70,7 @@ Item {
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
target: root
|
target: root
|
||||||
property: "implicitWidth"
|
property: "implicitHeight"
|
||||||
type: Anim.Emphasized
|
type: Anim.Emphasized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,14 +79,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
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,34 @@
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal redesign: center clock module — date (muted) + time (DemiBold),
|
||||||
|
// JetBrains Mono. The hover/active pill is applied by the bar module wrapper;
|
||||||
|
// this is just the content. Opens the Calendar popout.
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Caelestia.Config
|
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
StyledRect {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property color colour: Colours.palette.m3tertiary
|
spacing: 8
|
||||||
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)
|
|
||||||
|
|
||||||
implicitWidth: Tokens.sizes.bar.innerWidth
|
StyledText {
|
||||||
implicitHeight: layout.implicitHeight + root.padding * 2
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
text: Time.format("ddd d MMM")
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
font.weight: Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0)
|
StyledText {
|
||||||
radius: Tokens.rounding.full
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
text: Time.format("hh:mm")
|
||||||
ColumnLayout {
|
color: Theme.text
|
||||||
id: layout
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
anchors.centerIn: parent
|
font.weight: Font.DemiBold
|
||||||
spacing: Tokens.spacing.small
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
asynchronous: true
|
|
||||||
active: Config.bar.clock.showIcon
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
|
||||||
text: "calendar_month"
|
|
||||||
color: root.colour
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
visible: Config.bar.clock.showDate
|
|
||||||
|
|
||||||
horizontalAlignment: StyledText.AlignHCenter
|
|
||||||
text: Time.format("ddd\nd")
|
|
||||||
font: Tokens.font.body.small
|
|
||||||
color: root.colour
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: Config.bar.clock.showDate
|
|
||||||
implicitHeight: 1
|
|
||||||
color: Colours.palette.m3outlineVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: Time.hourStr
|
|
||||||
font: {
|
|
||||||
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / hourMetrics.width);
|
|
||||||
return root.font.width(scale * 100).letterSpacing(scale).build();
|
|
||||||
}
|
|
||||||
color: root.colour
|
|
||||||
|
|
||||||
TextMetrics {
|
|
||||||
id: hourMetrics
|
|
||||||
|
|
||||||
font: root.font.build()
|
|
||||||
text: Time.hourStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
Layout.topMargin: -parent.spacing - 4
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: Time.minuteStr
|
|
||||||
font: {
|
|
||||||
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / minMetrics.width);
|
|
||||||
return root.font.width(scale * 100).letterSpacing(scale).build();
|
|
||||||
}
|
|
||||||
color: root.colour
|
|
||||||
|
|
||||||
TextMetrics {
|
|
||||||
id: minMetrics
|
|
||||||
|
|
||||||
font: root.font.build()
|
|
||||||
text: Time.minuteStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
Layout.topMargin: -parent.spacing - 4
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
asynchronous: true
|
|
||||||
active: GlobalConfig.services.useTwelveHourClock
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
sourceComponent: StyledText {
|
|
||||||
text: Time.amPmStr.toLowerCase()
|
|
||||||
font: Tokens.font.body.builders.small.scale(0.9).build()
|
|
||||||
color: root.colour
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,24 @@ Item {
|
||||||
required property real borderThickness
|
required property real borderThickness
|
||||||
|
|
||||||
readonly property alias content: content
|
readonly property alias content: content
|
||||||
property real offsetScale: x > 0 || content.hasCurrent ? 0 : 1
|
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
|
||||||
|
|
||||||
visible: width > 0 && height > 0
|
visible: width > 0 && height > 0
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
implicitWidth: content.implicitWidth * (1 - offsetScale)
|
// NoSignal top bar: popouts drop DOWN from the bar. The collapsing axis is
|
||||||
implicitHeight: content.implicitHeight
|
// now height (was width for the vertical left bar); position along the bar
|
||||||
|
// is x (from the hovered widget's x-centre), the docked edge is the top.
|
||||||
|
implicitWidth: content.implicitWidth
|
||||||
|
implicitHeight: content.implicitHeight * (1 - offsetScale)
|
||||||
|
|
||||||
x: content.isDetached ? (parent.width - content.nonAnimWidth) / 2 : 0
|
y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0
|
||||||
y: {
|
x: {
|
||||||
if (content.isDetached)
|
if (content.isDetached)
|
||||||
return (parent.height - content.nonAnimHeight) / 2;
|
return (parent.width - content.nonAnimWidth) / 2;
|
||||||
|
|
||||||
const off = content.currentCenter - borderThickness - content.nonAnimHeight / 2;
|
const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2;
|
||||||
const diff = parent.height - Math.floor(off + content.nonAnimHeight);
|
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
return Math.max(off, 0);
|
return Math.max(off, 0);
|
||||||
|
|
@ -36,14 +39,14 @@ Item {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on y {
|
||||||
Anim {
|
Anim {
|
||||||
duration: content.animLength
|
duration: content.animLength
|
||||||
easing: content.animCurve
|
easing: content.animCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on y {
|
Behavior on x {
|
||||||
enabled: root.offsetScale < 1
|
enabled: root.offsetScale < 1
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
|
|
@ -58,8 +61,8 @@ Item {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
offsetScale: root.offsetScale
|
offsetScale: root.offsetScale
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.left: parent.left
|
anchors.top: parent.top
|
||||||
anchors.leftMargin: (-implicitWidth - 5) * root.offsetScale
|
anchors.topMargin: (-implicitHeight - 5) * root.offsetScale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,14 +149,14 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitHeight {
|
||||||
Anim {
|
Anim {
|
||||||
duration: root.animLength
|
duration: root.animLength
|
||||||
easing: root.animCurve
|
easing: root.animCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitWidth {
|
||||||
enabled: root.offsetScale < 1
|
enabled: root.offsetScale < 1
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ Item {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: Tokens.padding.large
|
anchors.margins: Tokens.padding.large
|
||||||
|
|
||||||
radius: Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ GridLayout {
|
||||||
Layout.preferredWidth: Tokens.sizes.dashboard.userWidth
|
Layout.preferredWidth: Tokens.sizes.dashboard.userWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
radius: Tokens.rounding.extraLarge
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
User {
|
User {
|
||||||
id: user
|
id: user
|
||||||
|
|
@ -37,7 +37,7 @@ GridLayout {
|
||||||
Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
|
Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
|
||||||
Layout.preferredHeight: weather.implicitHeight
|
Layout.preferredHeight: weather.implicitHeight
|
||||||
|
|
||||||
radius: Tokens.rounding.extraLarge * 1.5
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
SmallWeather {
|
SmallWeather {
|
||||||
id: weather
|
id: weather
|
||||||
|
|
@ -49,7 +49,7 @@ GridLayout {
|
||||||
Layout.preferredWidth: dateTime.implicitWidth
|
Layout.preferredWidth: dateTime.implicitWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
radius: Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
DateTime {
|
DateTime {
|
||||||
id: dateTime
|
id: dateTime
|
||||||
|
|
@ -63,7 +63,7 @@ GridLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: calendar.implicitHeight
|
Layout.preferredHeight: calendar.implicitHeight
|
||||||
|
|
||||||
radius: Tokens.rounding.extraLarge
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
Calendar {
|
Calendar {
|
||||||
id: calendar
|
id: calendar
|
||||||
|
|
@ -78,7 +78,7 @@ GridLayout {
|
||||||
Layout.preferredWidth: resources.implicitWidth
|
Layout.preferredWidth: resources.implicitWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
radius: Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
Resources {
|
Resources {
|
||||||
id: resources
|
id: resources
|
||||||
|
|
@ -92,7 +92,7 @@ GridLayout {
|
||||||
Layout.preferredWidth: media.implicitWidth
|
Layout.preferredWidth: media.implicitWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
radius: Tokens.rounding.extraLarge * 2
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
Media {
|
Media {
|
||||||
id: media
|
id: media
|
||||||
|
|
|
||||||
|
|
@ -102,11 +102,11 @@ Item {
|
||||||
CAnim {}
|
CAnim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "queue_music"
|
icon: "queue_music"
|
||||||
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
|
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
|
||||||
color: Colours.palette.m3onPrimaryContainer
|
color: Colours.palette.m3onPrimaryContainer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ Item {
|
||||||
sourceComponent: ColumnLayout {
|
sourceComponent: ColumnLayout {
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "tune"
|
icon: "tune"
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
|
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ Item {
|
||||||
implicitHeight: parent.implicitHeight * 2
|
implicitHeight: parent.implicitHeight * 2
|
||||||
|
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
radius: Tokens.rounding.full
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
|
|
@ -148,18 +148,18 @@ Item {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
implicitHeight: parent.height + Tokens.sizes.dashboard.tabIndicatorSpacing * 2
|
implicitHeight: parent.height + Tokens.sizes.dashboard.tabIndicatorSpacing * 2
|
||||||
|
|
||||||
radius: Tokens.rounding.medium
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
|
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
|
||||||
onClicked: root.dashState.currentTab = tab.TabBar.index
|
onClicked: root.dashState.currentTab = tab.TabBar.index
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottom: label.top
|
anchors.bottom: label.top
|
||||||
|
|
||||||
text: tab.iconName
|
icon: tab.iconName
|
||||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||||
fill: tab.current ? 1 : 0
|
fill: tab.current ? 1 : 0
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: bigInfoRow.implicitHeight + Tokens.padding.small
|
implicitHeight: bigInfoRow.implicitHeight + Tokens.padding.small
|
||||||
|
|
||||||
radius: Tokens.rounding.extraLarge * 2
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|
@ -76,9 +76,9 @@ Item {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.largeIncreased
|
spacing: Tokens.spacing.largeIncreased
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
text: Weather.icon
|
icon: Weather.icon
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build()
|
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build()
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
animate: true
|
animate: true
|
||||||
|
|
@ -155,7 +155,7 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
|
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
|
||||||
|
|
||||||
radius: Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -180,9 +180,9 @@ Item {
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: forecastItem.modelData.icon
|
icon: forecastItem.modelData.icon
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
}
|
}
|
||||||
|
|
@ -209,15 +209,15 @@ Item {
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 60
|
Layout.preferredHeight: 60
|
||||||
radius: Tokens.rounding.medium
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: detailRoot.icon
|
icon: detailRoot.icon
|
||||||
color: detailRoot.colour
|
color: detailRoot.colour
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
@ -252,8 +252,8 @@ Item {
|
||||||
|
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: weatherStat.icon
|
icon: weatherStat.icon
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
color: weatherStat.colour
|
color: weatherStat.colour
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ CustomMouseArea {
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
radius: pressed ? Tokens.rounding.small : Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
disabled: {
|
disabled: {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
|
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,10 @@ Item {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: res.icon
|
icon: res.icon
|
||||||
font: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
color: res.fgColour
|
color: res.fgColour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ Item {
|
||||||
|
|
||||||
Component.onCompleted: Weather.reload()
|
Component.onCompleted: Weather.reload()
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
animate: true
|
animate: true
|
||||||
text: Weather.icon
|
icon: Weather.icon
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(1.6).build()
|
fontStyle: Tokens.font.icon.builders.extraLarge.scale(1.6).build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ Item {
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
active: pfp.status !== Image.Ready
|
active: pfp.status !== Image.Ready
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: "person_add"
|
icon: "person_add"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
fill: 1
|
fill: 1
|
||||||
|
|
@ -120,9 +120,9 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "person_edit"
|
icon: "person_edit"
|
||||||
color: Colours.palette.m3onPrimary
|
color: Colours.palette.m3onPrimary
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
}
|
}
|
||||||
|
|
@ -187,9 +187,9 @@ Item {
|
||||||
CAnim {}
|
CAnim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "clock_arrow_up"
|
icon: "clock_arrow_up"
|
||||||
color: Colours.palette.m3onTertiaryContainer
|
color: Colours.palette.m3onTertiaryContainer
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +216,7 @@ Item {
|
||||||
|
|
||||||
implicitWidth: 10
|
implicitWidth: 10
|
||||||
implicitHeight: 10
|
implicitHeight: 10
|
||||||
radius: Tokens.rounding.full
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.palette.m3secondaryContainer
|
color: Colours.palette.m3secondaryContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ Item {
|
||||||
|
|
||||||
implicitWidth: 15
|
implicitWidth: 15
|
||||||
implicitHeight: 15
|
implicitHeight: 15
|
||||||
radius: Tokens.rounding.full
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.palette.m3secondaryContainer
|
color: Colours.palette.m3secondaryContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +240,7 @@ Item {
|
||||||
anchors.leftMargin: -Tokens.padding.medium
|
anchors.leftMargin: -Tokens.padding.medium
|
||||||
y: Tokens.padding.extraSmall
|
y: Tokens.padding.extraSmall
|
||||||
|
|
||||||
radius: Tokens.rounding.largeIncreased
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
color: Colours.palette.m3secondaryContainer
|
color: Colours.palette.m3secondaryContainer
|
||||||
implicitWidth: wmLabel.implicitWidth + Tokens.padding.medium * 2
|
implicitWidth: wmLabel.implicitWidth + Tokens.padding.medium * 2
|
||||||
implicitHeight: wmLabel.implicitHeight + Tokens.padding.small * 2
|
implicitHeight: wmLabel.implicitHeight + Tokens.padding.small * 2
|
||||||
|
|
@ -251,11 +251,11 @@ Item {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: wmIcon
|
id: wmIcon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "select_window"
|
icon: "select_window"
|
||||||
color: Colours.palette.m3onSecondaryContainer
|
color: Colours.palette.m3onSecondaryContainer
|
||||||
fontStyle: wmText.font
|
fontStyle: wmText.font
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ Item {
|
||||||
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
|
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
|
||||||
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
|
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
|
||||||
color: Colours.palette.m3primaryContainer
|
color: Colours.palette.m3primaryContainer
|
||||||
radius: Tokens.rounding.full
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
LoadingIndicator {
|
LoadingIndicator {
|
||||||
id: shape
|
id: shape
|
||||||
|
|
@ -212,9 +212,9 @@ Item {
|
||||||
sourceComponent: ColumnLayout {
|
sourceComponent: ColumnLayout {
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "sentiment_sad"
|
icon: "sentiment_sad"
|
||||||
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
|
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
|
||||||
color: Colours.palette.m3outline
|
color: Colours.palette.m3outline
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ Item {
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
|
||||||
text: "lyrics"
|
icon: "lyrics"
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
|
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
|
||||||
group: blobGroup
|
group: blobGroup
|
||||||
radius: Tokens.rounding.medium
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
Behavior on anchors.margins {
|
Behavior on anchors.margins {
|
||||||
Anim {}
|
Anim {}
|
||||||
|
|
@ -50,7 +50,7 @@ Item {
|
||||||
implicitHeight: parent.height
|
implicitHeight: parent.height
|
||||||
|
|
||||||
group: blobGroup
|
group: blobGroup
|
||||||
radius: Tokens.rounding.medium
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
deformScale: 0.00001
|
deformScale: 0.00001
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
|
|
@ -197,11 +197,11 @@ Item {
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onClicked: root.open = !root.open
|
onClicked: root.open = !root.open
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "more_vert"
|
icon: "more_vert"
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ StyledClippingRect {
|
||||||
property real animPerc: UPower.displayDevice.percentage
|
property real animPerc: UPower.displayDevice.percentage
|
||||||
|
|
||||||
color: Colours.palette.m3secondaryContainer
|
color: Colours.palette.m3secondaryContainer
|
||||||
radius: Tokens.rounding.large
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
implicitWidth: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && Gpu.type !== Gpu.None) || Config.dashboard.performance.showStorage || Config.dashboard.performance.showMemory ? Tokens.sizes.dashboard.perfBattWidth : Tokens.sizes.dashboard.perfBattWidthSingle
|
implicitWidth: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && Gpu.type !== Gpu.None) || Config.dashboard.performance.showStorage || Config.dashboard.performance.showMemory ? Tokens.sizes.dashboard.perfBattWidth : Tokens.sizes.dashboard.perfBattWidthSingle
|
||||||
implicitHeight: Tokens.sizes.dashboard.perfBattHeight
|
implicitHeight: Tokens.sizes.dashboard.perfBattHeight
|
||||||
|
|
@ -39,7 +39,7 @@ StyledClippingRect {
|
||||||
implicitHeight: parent.height * root.animPerc
|
implicitHeight: parent.height * root.animPerc
|
||||||
|
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
radius: Tokens.rounding.extraSmall
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Contents {
|
Contents {
|
||||||
|
|
@ -65,9 +65,9 @@ StyledClippingRect {
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.leftMargin: -Tokens.padding.extraSmall
|
Layout.leftMargin: -Tokens.padding.extraSmall
|
||||||
text: "battery_full"
|
icon: "battery_full"
|
||||||
color: contents.accentColour
|
color: contents.accentColour
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +115,8 @@ StyledClippingRect {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "bolt"
|
icon: "bolt"
|
||||||
color: contents.accentColour
|
color: contents.accentColour
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
fill: 1
|
fill: 1
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ StyledRect {
|
||||||
required property real temperature
|
required property real temperature
|
||||||
|
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
radius: Tokens.rounding.extraLarge
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
implicitWidth: Tokens.sizes.dashboard.perfHeroCardWidth
|
implicitWidth: Tokens.sizes.dashboard.perfHeroCardWidth
|
||||||
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Tokens.spacing.large, usageShape.implicitHeight + usageLabel.implicitHeight) + Tokens.padding.large * 2
|
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Tokens.spacing.large, usageShape.implicitHeight + usageLabel.implicitHeight) + Tokens.padding.large * 2
|
||||||
|
|
@ -40,11 +40,11 @@ StyledRect {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.icon
|
icon: root.icon
|
||||||
color: root.accent
|
color: root.accent
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -84,9 +84,9 @@ StyledRect {
|
||||||
Layout.leftMargin: -Tokens.padding.extraSmall
|
Layout.leftMargin: -Tokens.padding.extraSmall
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
|
||||||
text: root.temperature > 90 ? "thermometer_alert" : "thermometer"
|
icon: root.temperature > 90 ? "thermometer_alert" : "thermometer"
|
||||||
color: root.temperature > 90 ? Colours.palette.m3error : root.accent
|
color: root.temperature > 90 ? Colours.palette.m3error : root.accent
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
fill: 1
|
fill: 1
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ StyledRect {
|
||||||
readonly property color accent: Colours.palette.m3tertiary
|
readonly property color accent: Colours.palette.m3tertiary
|
||||||
|
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
radius: Tokens.rounding.medium
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
implicitWidth: layout.implicitWidth + Tokens.padding.extraLargeIncreased * 2
|
implicitWidth: layout.implicitWidth + Tokens.padding.extraLargeIncreased * 2
|
||||||
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
||||||
|
|
@ -31,8 +31,8 @@ StyledRect {
|
||||||
Layout.leftMargin: -Tokens.padding.extraSmall
|
Layout.leftMargin: -Tokens.padding.extraSmall
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "memory_alt"
|
icon: "memory_alt"
|
||||||
fill: 1
|
fill: 1
|
||||||
color: root.accent
|
color: root.accent
|
||||||
fontStyle: Tokens.font.icon.builders.medium.weight(Font.DemiBold).build() // DemiBold to fix fill issues
|
fontStyle: Tokens.font.icon.builders.medium.weight(Font.DemiBold).build() // DemiBold to fix fill issues
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ StyledRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
radius: Tokens.rounding.extraLarge
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth
|
implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth
|
||||||
implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight
|
implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight
|
||||||
|
|
@ -30,8 +30,8 @@ StyledRect {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "swap_vert"
|
icon: "swap_vert"
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -105,8 +105,8 @@ StyledRect {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "download"
|
icon: "download"
|
||||||
color: Colours.palette.m3tertiary
|
color: Colours.palette.m3tertiary
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +136,8 @@ StyledRect {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "upload"
|
icon: "upload"
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -167,8 +167,8 @@ StyledRect {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "history"
|
icon: "history"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ StyledRect {
|
||||||
readonly property real percentage: Storage.primaryDisk?.perc ?? 0
|
readonly property real percentage: Storage.primaryDisk?.perc ?? 0
|
||||||
|
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
radius: Tokens.rounding.extraExtraLarge
|
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
|
||||||
|
|
||||||
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
|
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
|
||||||
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
||||||
|
|
@ -55,9 +55,9 @@ StyledRect {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "hard_drive"
|
icon: "hard_drive"
|
||||||
color: root.accent
|
color: root.accent
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ StyledWindow {
|
||||||
|
|
||||||
property real fsTransitionProg: hasFullscreen ? 1 : 0
|
property real fsTransitionProg: hasFullscreen ? 1 : 0
|
||||||
readonly property real sdfBorderOffset: 2 * fsTransitionProg // SDFs joins are not exact, so offset by 2px to ensure nothing shows
|
readonly property real sdfBorderOffset: 2 * fsTransitionProg // SDFs joins are not exact, so offset by 2px to ensure nothing shows
|
||||||
readonly property real borderThickness: contentItem.Config.border.thickness * (1 - fsTransitionProg)
|
readonly property real borderThickness: 0 // NoSignal: no screen-edge frame (NsBar owns the top)
|
||||||
readonly property real borderRounding: contentItem.Config.border.rounding * (1 - fsTransitionProg)
|
readonly property real borderRounding: 0 // NoSignal: no rounded screen frame
|
||||||
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
|
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
|
||||||
readonly property real borderLayoutThickness: hasFullscreen ? 0 : contentItem.Config.border.thickness
|
readonly property real borderLayoutThickness: hasFullscreen ? 0 : contentItem.Config.border.thickness
|
||||||
|
|
||||||
|
|
@ -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: -anchors.margins - root.sdfBorderOffset // NoSignal: no top frame
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,8 +198,10 @@ StyledWindow {
|
||||||
id: osdBg
|
id: osdBg
|
||||||
|
|
||||||
panel: panels.osdWrapper
|
panel: panels.osdWrapper
|
||||||
deformAmount: 0.25
|
deformAmount: 0 // NoSignal: square OSD (no organic blob deformation)
|
||||||
x: panels.osdWrapper.x + panels.osd.x + bar.implicitWidth
|
radius: 0
|
||||||
|
opacity: 0 // NoSignal: OSD draws its own flat square glass bg (osd/Content)
|
||||||
|
x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
|
||||||
implicitWidth: panels.osd.width
|
implicitWidth: panels.osd.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +209,12 @@ StyledWindow {
|
||||||
id: notifsBg
|
id: notifsBg
|
||||||
|
|
||||||
panel: panels.notifications
|
panel: panels.notifications
|
||||||
|
deformAmount: 0 // NoSignal: square notification toasts
|
||||||
|
radius: 0
|
||||||
|
opacity: 0
|
||||||
|
visible: false
|
||||||
|
implicitWidth: 0 // collapse the blob entirely (toasts draw their own square cards)
|
||||||
|
implicitHeight: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelBg {
|
PanelBg {
|
||||||
|
|
@ -226,7 +234,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 +300,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 +319,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
|
||||||
|
|
|
||||||
|
|
@ -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: 0 // NoSignal: NsBar owns the top exclusive zone
|
||||||
}
|
}
|
||||||
|
|
||||||
ExclusionZone {
|
ExclusionZone {
|
||||||
|
|
@ -32,7 +32,7 @@ Scope {
|
||||||
component ExclusionZone: StyledWindow {
|
component ExclusionZone: StyledWindow {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
name: "border-exclusion"
|
name: "border-exclusion"
|
||||||
exclusiveZone: contentItem.Config.border.thickness
|
exclusiveZone: 0 // NoSignal: no screen-edge frame
|
||||||
mask: Region {}
|
mask: Region {}
|
||||||
implicitWidth: 1
|
implicitWidth: 1
|
||||||
implicitHeight: 1
|
implicitHeight: 1
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -70,7 +70,7 @@ Item {
|
||||||
sessionPanel: sessionWrapper
|
sessionPanel: sessionWrapper
|
||||||
utilitiesPanel: utilities
|
utilitiesPanel: utilities
|
||||||
|
|
||||||
anchors.top: parent.top
|
y: 46 // NoSignal: drop the toasts below the 38px NsBar (+ gap)
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,14 @@ Item {
|
||||||
|
|
||||||
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
|
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: searchIcon
|
id: searchIcon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: root.padding
|
anchors.leftMargin: root.padding
|
||||||
|
|
||||||
text: "search"
|
icon: "search"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +145,7 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: clearIcon
|
id: clearIcon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
@ -163,7 +163,7 @@ Item {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
text: "close"
|
icon: "close"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,8 @@ Item {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
|
icon: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ Item {
|
||||||
anchors.rightMargin: Tokens.padding.medium
|
anchors.rightMargin: Tokens.padding.medium
|
||||||
anchors.margins: Tokens.padding.small
|
anchors.margins: Tokens.padding.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: root.modelData?.icon ?? ""
|
icon: root.modelData?.icon ?? ""
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.builders.large.scale(1.3).build()
|
fontStyle: Tokens.font.icon.builders.large.scale(1.3).build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ Item {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
active: root.modelData && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, root.modelData.id)
|
active: root.modelData && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, root.modelData.id)
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: "favorite"
|
icon: "favorite"
|
||||||
fill: 1
|
fill: 1
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ Item {
|
||||||
|
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "function"
|
icon: "function"
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
@ -105,14 +105,14 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Tokens.padding.medium
|
anchors.rightMargin: Tokens.padding.medium
|
||||||
|
|
||||||
text: "open_in_new"
|
icon: "open_in_new"
|
||||||
color: Colours.palette.m3onTertiary
|
color: Colours.palette.m3onTertiary
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,8 @@ Item {
|
||||||
|
|
||||||
active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme
|
active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: "check"
|
icon: "check"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ Item {
|
||||||
anchors.rightMargin: Tokens.padding.medium
|
anchors.rightMargin: Tokens.padding.medium
|
||||||
anchors.margins: Tokens.padding.small
|
anchors.margins: Tokens.padding.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
text: root.modelData?.icon ?? ""
|
icon: root.modelData?.icon ?? ""
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
@ -68,8 +68,8 @@ Item {
|
||||||
|
|
||||||
active: root.modelData?.variant === Schemes.currentVariant
|
active: root.modelData?.variant === Schemes.currentVariant
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: "check"
|
icon: "check"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,9 @@ Item {
|
||||||
implicitWidth: Tokens.sizes.launcher.wallpaperWidth
|
implicitWidth: Tokens.sizes.launcher.wallpaperWidth
|
||||||
implicitHeight: implicitWidth / 16 * 9
|
implicitHeight: implicitWidth / 16 * 9
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "image"
|
icon: "image"
|
||||||
color: Colours.tPalette.m3outline
|
color: Colours.tPalette.m3outline
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build()
|
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal lock screen surface (interface redesign 2026-06-14).
|
||||||
|
// Secure ext-session-lock content: blurred wallpaper + scrim, top status row,
|
||||||
|
// centred clock/date/avatar/username/password, bottom now-playing strip.
|
||||||
|
// Reuses caelestia's Pam (buffer + handleKey + state) for PAM auth and the
|
||||||
|
// WlSessionLock unlock signal — input is fed to pam.handleKey, never compared
|
||||||
|
// in QML. JetBrains Mono / Nerd Font / NoSignal Theme tokens.
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell.Services.UPower
|
||||||
import Caelestia.Config
|
import Caelestia.Config
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
id: root
|
id: root
|
||||||
|
|
@ -13,153 +24,26 @@ WlSessionLockSurface {
|
||||||
required property WlSessionLock lock
|
required property WlSessionLock lock
|
||||||
required property Pam pam
|
required property Pam pam
|
||||||
|
|
||||||
readonly property alias unlocking: unlockAnim.running
|
readonly property bool errored: root.pam.state === "error" || root.pam.state === "fail"
|
||||||
|
|
||||||
contentItem.Config.screen: screen.name
|
contentItem.Config.screen: screen?.name ?? ""
|
||||||
contentItem.Tokens.screen: screen.name
|
contentItem.Tokens.screen: screen?.name ?? ""
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onUnlock(): void {
|
function onUnlock(): void {
|
||||||
unlockAnim.start();
|
root.lock.locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
target: root.lock
|
target: root.lock
|
||||||
}
|
}
|
||||||
|
|
||||||
SequentialAnimation {
|
// ── Blurred wallpaper + scrim ──────────────────────────────────────────
|
||||||
id: unlockAnim
|
|
||||||
|
|
||||||
ParallelAnimation {
|
|
||||||
Anim {
|
|
||||||
target: lockContent
|
|
||||||
properties: "implicitWidth,implicitHeight"
|
|
||||||
to: lockContent.size
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockBg
|
|
||||||
property: "radius"
|
|
||||||
to: lockContent.radius
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: content
|
|
||||||
property: "scale"
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: content
|
|
||||||
property: "opacity"
|
|
||||||
to: 0
|
|
||||||
type: Anim.StandardSmall
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockIcon
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
type: Anim.StandardLarge
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: background
|
|
||||||
property: "opacity"
|
|
||||||
to: 0
|
|
||||||
type: Anim.StandardLarge
|
|
||||||
}
|
|
||||||
SequentialAnimation {
|
|
||||||
PauseAnimation {
|
|
||||||
duration: Tokens.anim.durations.small
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
type: Anim.Standard
|
|
||||||
target: lockContent
|
|
||||||
property: "opacity"
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PropertyAction {
|
|
||||||
target: root.lock
|
|
||||||
property: "locked"
|
|
||||||
value: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ParallelAnimation {
|
|
||||||
id: initAnim
|
|
||||||
|
|
||||||
running: true
|
|
||||||
|
|
||||||
Anim {
|
|
||||||
target: background
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
type: Anim.StandardLarge
|
|
||||||
}
|
|
||||||
SequentialAnimation {
|
|
||||||
ParallelAnimation {
|
|
||||||
Anim {
|
|
||||||
target: lockContent
|
|
||||||
property: "scale"
|
|
||||||
to: 1
|
|
||||||
type: Anim.FastSpatial
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockContent
|
|
||||||
property: "rotation"
|
|
||||||
to: 360
|
|
||||||
duration: Tokens.anim.durations.expressiveFastSpatial
|
|
||||||
easing: Tokens.anim.standardAccel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ParallelAnimation {
|
|
||||||
Anim {
|
|
||||||
target: lockIcon
|
|
||||||
property: "rotation"
|
|
||||||
to: 360
|
|
||||||
easing: Tokens.anim.standardDecel
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
target: lockIcon
|
|
||||||
property: "opacity"
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
target: content
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: content
|
|
||||||
property: "scale"
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockBg
|
|
||||||
property: "radius"
|
|
||||||
to: lockContent.Tokens.rounding.extraLarge * 1.5
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockContent
|
|
||||||
property: "implicitWidth"
|
|
||||||
to: (root.screen?.height ?? 0) * lockContent.Tokens.sizes.lock.heightMult * lockContent.Tokens.sizes.lock.ratio
|
|
||||||
}
|
|
||||||
Anim {
|
|
||||||
target: lockContent
|
|
||||||
property: "implicitHeight"
|
|
||||||
to: (root.screen?.height ?? 0) * lockContent.Tokens.sizes.lock.heightMult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreencopyView {
|
ScreencopyView {
|
||||||
id: background
|
id: background
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
captureSource: root.screen
|
captureSource: root.screen
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
|
|
@ -168,57 +52,265 @@ WlSessionLockSurface {
|
||||||
blur: 1
|
blur: 1
|
||||||
blurMax: 64
|
blurMax: 64
|
||||||
blurMultiplier: 1
|
blurMultiplier: 1
|
||||||
|
brightness: -0.38
|
||||||
|
saturation: 0.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(8 / 255, 10 / 255, 14 / 255, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Keyboard capture (feed PAM; Escape does nothing) ───────────────────
|
||||||
Item {
|
Item {
|
||||||
id: lockContent
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
Component.onCompleted: forceActiveFocus()
|
||||||
|
Keys.onPressed: event => root.pam.handleKey(event)
|
||||||
|
}
|
||||||
|
|
||||||
readonly property int size: lockIcon.implicitHeight + Tokens.padding.large * 4
|
// ── Top status row ─────────────────────────────────────────────────────
|
||||||
readonly property int radius: size / 4 * Tokens.rounding.scale
|
RowLayout {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 22
|
||||||
|
anchors.leftMargin: 28
|
||||||
|
anchors.rightMargin: 28
|
||||||
|
|
||||||
anchors.centerIn: parent
|
RowLayout {
|
||||||
implicitWidth: size
|
spacing: 7
|
||||||
implicitHeight: size
|
NsIcon {
|
||||||
|
icon: "lock"
|
||||||
rotation: 180
|
color: Theme.textMuted
|
||||||
scale: 0
|
size: 15
|
||||||
|
}
|
||||||
StyledRect {
|
StyledText {
|
||||||
id: lockBg
|
text: "Locked"
|
||||||
|
color: Theme.textMuted
|
||||||
anchors.fill: parent
|
font.family: Theme.font.family
|
||||||
color: Colours.palette.m3surface
|
font.pixelSize: Theme.font.bar
|
||||||
radius: parent.radius
|
|
||||||
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
blurMax: 15
|
|
||||||
shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
Item {
|
||||||
id: lockIcon
|
Layout.fillWidth: true
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "lock"
|
|
||||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(4).weight(Font.Bold).build()
|
|
||||||
rotation: 180
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Content {
|
RowLayout {
|
||||||
id: content
|
spacing: 14
|
||||||
|
|
||||||
anchors.centerIn: parent
|
NsIcon {
|
||||||
width: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult * Tokens.sizes.lock.ratio - Tokens.padding.extraLargeIncreased
|
icon: Nmcli.active ? Icons.getNetworkIcon(Nmcli.active.strength ?? 0) : "wifi_off"
|
||||||
height: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult - Tokens.padding.extraLargeIncreased
|
color: Theme.textMuted
|
||||||
|
size: 15
|
||||||
|
}
|
||||||
|
NsIcon {
|
||||||
|
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth_connected" : "bluetooth_disabled"
|
||||||
|
color: Theme.textMuted
|
||||||
|
size: 15
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 6
|
||||||
|
visible: UPower.displayDevice.isLaptopBattery
|
||||||
|
NsIcon {
|
||||||
|
icon: Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged].includes(UPower.displayDevice.state))
|
||||||
|
color: Theme.textMuted
|
||||||
|
size: 15
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lock: root
|
// ── Centre block ───────────────────────────────────────────────────────
|
||||||
opacity: 0
|
ColumnLayout {
|
||||||
scale: 0
|
anchors.centerIn: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: Time.format("hh:mm")
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: 108
|
||||||
|
font.weight: Font.ExtraLight
|
||||||
|
font.letterSpacing: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: Time.format("ddd, d MMMM yyyy")
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: 64
|
||||||
|
implicitWidth: 74
|
||||||
|
implicitHeight: 74
|
||||||
|
radius: 0
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Vertical
|
||||||
|
GradientStop {
|
||||||
|
position: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: Theme.blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: "person"
|
||||||
|
color: Theme.onAccent
|
||||||
|
size: 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: 14
|
||||||
|
text: SysInfo.user || "user"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: 16
|
||||||
|
implicitWidth: 312
|
||||||
|
implicitHeight: 44
|
||||||
|
radius: Theme.radius.panel
|
||||||
|
color: Qt.rgba(17 / 255, 20 / 255, 26 / 255, 0.55)
|
||||||
|
border.width: 1
|
||||||
|
border.color: root.errored ? Qt.rgba(224 / 255, 116 / 255, 106 / 255, 0.6) : Qt.rgba(1, 1, 1, 0.1)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 14
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: "key"
|
||||||
|
color: Theme.textMuted
|
||||||
|
size: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.pam.buffer.length > 0 ? "•".repeat(root.pam.buffer.length) : "Enter password"
|
||||||
|
color: root.pam.buffer.length > 0 ? Theme.text : Theme.textFaint
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.body
|
||||||
|
font.letterSpacing: root.pam.buffer.length > 0 ? 2 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: "login"
|
||||||
|
color: root.pam.buffer.length > 0 ? Theme.accent : Theme.textFaint
|
||||||
|
size: 16
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -6
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: if (root.pam.buffer.length > 0)
|
||||||
|
root.pam.passwd.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: 8
|
||||||
|
Layout.preferredHeight: 14
|
||||||
|
text: root.errored ? "Incorrect — try again" : (root.pam.lockMessage || "")
|
||||||
|
color: Theme.danger
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bottom strip ───────────────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottomMargin: 26
|
||||||
|
anchors.leftMargin: 28
|
||||||
|
anchors.rightMargin: 28
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 10
|
||||||
|
visible: Players.active !== null
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 42
|
||||||
|
implicitHeight: 42
|
||||||
|
radius: Theme.radius.panel
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Vertical
|
||||||
|
GradientStop {
|
||||||
|
position: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: Theme.blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: "music_note"
|
||||||
|
color: Theme.onAccent
|
||||||
|
size: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: Players.active?.trackTitle || ""
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.maximumWidth: 280
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: Players.active?.trackArtist || ""
|
||||||
|
color: Theme.textMuted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.maximumWidth: 280
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Press Enter to unlock"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,12 @@ Scope {
|
||||||
PamContext {
|
PamContext {
|
||||||
id: passwd
|
id: passwd
|
||||||
|
|
||||||
config: "passwd"
|
// NoSignal: faillock-free lock auth (finding F1). The stock "passwd"
|
||||||
|
// service routes through pam_faillock, which can lock the user out of
|
||||||
|
// their OWN session after a few failed unlocks — refusing even the
|
||||||
|
// correct password until /run/faillock clears on reboot. The bundled
|
||||||
|
// "caelestia" service (assets/pam.d/caelestia) uses plain pam_unix.
|
||||||
|
config: "caelestia"
|
||||||
configDirectory: Quickshell.shellDir + "/assets/pam.d"
|
configDirectory: Quickshell.shellDir + "/assets/pam.d"
|
||||||
|
|
||||||
onMessageChanged: {
|
onMessageChanged: {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Item {
|
||||||
|
|
||||||
onClose: root.close()
|
onClose: root.close()
|
||||||
}
|
}
|
||||||
property color blobColour: Colours.tPalette.m3surfaceContainerLow
|
property color blobColour: Theme.panelBg // NoSignal: fixed dark-glass window surface
|
||||||
|
|
||||||
signal close
|
signal close
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ Item {
|
||||||
BlobGroup {
|
BlobGroup {
|
||||||
id: blobGroup
|
id: blobGroup
|
||||||
|
|
||||||
smoothing: root.Tokens.rounding.medium
|
smoothing: 0 // NoSignal: crisp corners (no organic blob smoothing)
|
||||||
color: root.blobColour
|
color: root.blobColour
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
group: blobGroup
|
group: blobGroup
|
||||||
opacity: root.blobColour.a
|
opacity: root.blobColour.a
|
||||||
radius: Tokens.rounding.large
|
radius: Theme.radius.panel // NoSignal: sharp 4px corners
|
||||||
|
|
||||||
borderLeft: navPane.width + navPane.anchors.margins * 2
|
borderLeft: navPane.width + navPane.anchors.margins * 2
|
||||||
borderRight: Tokens.padding.medium
|
borderRight: Tokens.padding.medium
|
||||||
|
|
|
||||||
|
|
@ -73,10 +73,20 @@ QtObject {
|
||||||
|
|
||||||
// System
|
// System
|
||||||
Component {
|
Component {
|
||||||
PlaceholderComp {}
|
// Updates (NoSignal)
|
||||||
|
StackPage {
|
||||||
|
Component {
|
||||||
|
UpdatesPage {}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Component {
|
Component {
|
||||||
PlaceholderComp {}
|
// Additions (NoSignal)
|
||||||
|
StackPage {
|
||||||
|
Component {
|
||||||
|
AdditionsPage {}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Shell
|
// Shell
|
||||||
|
|
@ -155,11 +165,11 @@ QtObject {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.padding.extraSmall
|
spacing: Tokens.padding.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "handyman"
|
icon: "handyman"
|
||||||
color: Colours.palette.m3outlineVariant
|
color: Colours.palette.m3outlineVariant
|
||||||
font: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ QtObject {
|
||||||
category: "system"
|
category: "system"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: qsTr("Plugins"),
|
label: qsTr("Additions"),
|
||||||
icon: "extension",
|
icon: "extension",
|
||||||
description: qsTr("Manage plugins"),
|
description: qsTr("Install optional software"),
|
||||||
category: "system"
|
category: "system"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,13 @@ ItemList {
|
||||||
radius: Tokens.rounding.full
|
radius: Tokens.rounding.full
|
||||||
color: device.active ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
color: device.active ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: devIcon
|
id: devIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.iconName
|
icon: root.iconName
|
||||||
color: device.active ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
color: device.active ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
fill: device.active ? 1 : 0
|
fill: device.active ? 1 : 0
|
||||||
|
|
||||||
Behavior on fill {
|
Behavior on fill {
|
||||||
|
|
@ -80,10 +80,10 @@ ItemList {
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "check"
|
icon: "check"
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
opacity: device.active ? 1 : 0
|
opacity: device.active ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
|
|
||||||
73
modules/nexus/common/CachyRepoToggleRow.qml
Normal file
73
modules/nexus/common/CachyRepoToggleRow.qml
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// CachyRepoToggleRow.qml (NoSignal) — Settings -> Services toggle to enable /
|
||||||
|
// disable the CachyOS pacman repositories. Auto-detects the best x86-64 microarch
|
||||||
|
// tier the CPU supports (v4 > v3). Live state polls `nosignal-cachy-repo status`
|
||||||
|
// (no root). enable/disable run in a floating terminal so the pacman/download
|
||||||
|
// output stays visible; the grant is passwordless via sudo -n
|
||||||
|
// (/etc/sudoers.d/02-nosignal-cachy). enable adds repos, converts userspace to
|
||||||
|
// the optimized builds (pacman -Suu, pacman pinned stock), and installs the
|
||||||
|
// linux-cachyos kernel; disable reverts to stock and removes the cachy kernel
|
||||||
|
// (keeping stock `linux` bootable). A reboot switches the running kernel.
|
||||||
|
//
|
||||||
|
// Untracked file under modules/nexus/common — auto-discovered as the type
|
||||||
|
// `CachyRepoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
|
||||||
|
// upgrades; pairs with the one-line insert in ServicesPage.qml.
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool repoOn: false // NB: not `enabled` (reserved Item property)
|
||||||
|
|
||||||
|
text: qsTr("CachyOS repositories")
|
||||||
|
subtext: repoOn
|
||||||
|
? qsTr("On — CachyOS optimized builds + linux-cachyos kernel (reboot to run it)")
|
||||||
|
: qsTr("Switch to CachyOS optimized builds + linux-cachyos kernel (reboot after)")
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
if (checked)
|
||||||
|
enableProc.running = true;
|
||||||
|
else
|
||||||
|
disableProc.running = true;
|
||||||
|
reconcile.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- live state -----------------------------------------------------------
|
||||||
|
Process {
|
||||||
|
id: statusProc
|
||||||
|
command: ["/usr/local/bin/nosignal-cachy-repo", "status"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.repoOn = text.trim() === "enabled";
|
||||||
|
root.checked = root.repoOn; // drive switch from real state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- actions (passwordless; shown in a terminal for pacman output) ---------
|
||||||
|
Process {
|
||||||
|
id: enableProc
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo enable; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: reconcile.restart()
|
||||||
|
}
|
||||||
|
Process {
|
||||||
|
id: disableProc
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo disable; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: reconcile.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- polling --------------------------------------------------------------
|
||||||
|
Timer {
|
||||||
|
interval: 5000
|
||||||
|
repeat: true
|
||||||
|
running: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: reconcile
|
||||||
|
interval: 2000
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,10 @@ StyledRect {
|
||||||
property bool first
|
property bool first
|
||||||
property bool last
|
property bool last
|
||||||
|
|
||||||
color: Colours.tPalette.m3surfaceContainer
|
// NoSignal: dark-glass cards, 4px outer corners (connected-list look)
|
||||||
topLeftRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
color: Theme.fillSubtle
|
||||||
topRightRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
topLeftRadius: first ? Theme.radius.panel : 0
|
||||||
bottomLeftRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
topRightRadius: first ? Theme.radius.panel : 0
|
||||||
bottomRightRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
bottomLeftRadius: last ? Theme.radius.panel : 0
|
||||||
|
bottomRightRadius: last ? Theme.radius.panel : 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,11 @@ ConnectedRect {
|
||||||
sourceComponent: ColumnLayout {
|
sourceComponent: ColumnLayout {
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: root.placeholderIcon
|
icon: root.placeholderIcon
|
||||||
color: Colours.palette.m3outline
|
color: Colours.palette.m3outline
|
||||||
font: Tokens.font.icon.large
|
fontStyle: Tokens.font.icon.large
|
||||||
animate: true
|
animate: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import qs.modules.nexus.common
|
||||||
ConnectedRect {
|
ConnectedRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias icon: icon.text
|
property alias icon: icon.icon
|
||||||
property alias label: label.text
|
property alias label: label.text
|
||||||
property alias status: status.text
|
property alias status: status.text
|
||||||
|
|
||||||
|
|
@ -32,11 +32,11 @@ ConnectedRect {
|
||||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -63,10 +63,10 @@ ConnectedRect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "chevron_right"
|
icon: "chevron_right"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import qs.modules.nexus.common
|
||||||
ConnectedRect {
|
ConnectedRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias icon: icon.text
|
property alias icon: icon.icon
|
||||||
property alias label: label.text
|
property alias label: label.text
|
||||||
property alias valueLabel: valueLabel.text
|
property alias valueLabel: valueLabel.text
|
||||||
property real value
|
property real value
|
||||||
|
|
@ -28,11 +28,11 @@ ConnectedRect {
|
||||||
anchors.topMargin: Tokens.padding.large
|
anchors.topMargin: Tokens.padding.large
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
|
||||||
102
modules/nexus/common/SudoToggleRow.qml
Normal file
102
modules/nexus/common/SudoToggleRow.qml
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
// SudoToggleRow.qml (NoSignal) — Settings -> Services toggle for time-boxed
|
||||||
|
// passwordless sudo. Reflects live state by polling `nosignal-sudo-toggle
|
||||||
|
// status` (no root needed); enabling opens a floating terminal for the ONE
|
||||||
|
// password prompt, disabling runs passwordless inside the active window.
|
||||||
|
//
|
||||||
|
// Untracked file under modules/nexus/common — auto-discovered as the type
|
||||||
|
// `SudoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
|
||||||
|
// upgrades; only the one-line insert in ServicesPage.qml is re-applied by hook.
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
property int remaining: 0
|
||||||
|
|
||||||
|
text: qsTr("Passwordless sudo (15 min)")
|
||||||
|
subtext: active
|
||||||
|
? qsTr("On — %1 min left. Auto-reverts; a reboot also clears it.").arg(remaining)
|
||||||
|
: qsTr("Run sudo without a password for 15 minutes, then it reverts")
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
if (checked) {
|
||||||
|
enableProc.running = true; // needs a password -> floating terminal
|
||||||
|
focusTimer.ticks = 0;
|
||||||
|
focusTimer.restart(); // then pull keyboard focus to the prompt
|
||||||
|
} else {
|
||||||
|
disableProc.running = true; // no password inside the active window
|
||||||
|
}
|
||||||
|
reconcile.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- live state -----------------------------------------------------------
|
||||||
|
Process {
|
||||||
|
id: statusProc
|
||||||
|
command: ["nosignal-sudo-toggle", "status"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const p = text.trim().split(/\s+/);
|
||||||
|
root.active = p[0] === "active";
|
||||||
|
root.remaining = parseInt(p[1] || "0") || 0;
|
||||||
|
root.checked = root.active; // drive switch from real state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- actions --------------------------------------------------------------
|
||||||
|
Process {
|
||||||
|
id: enableProc
|
||||||
|
// Dedicated window class (nosignal-sudo) so the shipped hypr windowrules
|
||||||
|
// (float/center/pin/stayfocused) force this prompt to GRAB keyboard focus.
|
||||||
|
// With the shared TUI.float class and no focus rule the prompt did not get
|
||||||
|
// keystrokes -> three blank tries tripped pam_faillock and the switch just
|
||||||
|
// snapped back with no feedback (hardware bug 2026-06-20). `enable-tui`
|
||||||
|
// runs the sudo prompt as the user and keeps the terminal open on failure
|
||||||
|
// so the reason is visible instead of vanishing.
|
||||||
|
command: ["kitty", "--class", "nosignal-sudo", "-e", "nosignal-sudo-toggle", "enable-tui"]
|
||||||
|
}
|
||||||
|
Process {
|
||||||
|
id: disableProc
|
||||||
|
command: ["sudo", "-n", "nosignal-sudo-toggle", "disable"]
|
||||||
|
}
|
||||||
|
// Force keyboard focus onto the password prompt once it has mapped. Hyprland
|
||||||
|
// 0.55's match: windowrule grammar has no working `stayfocused`, so we
|
||||||
|
// "activate it" from here instead (the prompt uses the dedicated nosignal-sudo
|
||||||
|
// class). Two nudges cover slow terminal startup.
|
||||||
|
Process {
|
||||||
|
id: focusProc
|
||||||
|
command: ["hyprctl", "dispatch", "focuswindow", "class:^(nosignal-sudo)$"]
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: focusTimer
|
||||||
|
interval: 400
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: false
|
||||||
|
property int ticks: 0
|
||||||
|
onTriggered: {
|
||||||
|
focusProc.running = true;
|
||||||
|
ticks += 1;
|
||||||
|
if (ticks >= 3) {
|
||||||
|
ticks = 0;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- polling --------------------------------------------------------------
|
||||||
|
Timer {
|
||||||
|
interval: 5000
|
||||||
|
repeat: true
|
||||||
|
running: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: reconcile
|
||||||
|
interval: 2000
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,11 +86,11 @@ VerticalFadeFlickable {
|
||||||
radius: Tokens.rounding.full
|
radius: Tokens.rounding.full
|
||||||
color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenterOffset: 1
|
anchors.verticalCenterOffset: 1
|
||||||
|
|
||||||
text: item.modelData.icon
|
icon: item.modelData.icon
|
||||||
color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||||
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
|
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
|
||||||
grade: 25
|
grade: 25
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ StyledRect {
|
||||||
|
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "search"
|
icon: "search"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Caelestia
|
import Caelestia
|
||||||
import Caelestia.Config
|
import Caelestia.Config
|
||||||
|
|
@ -61,22 +62,27 @@ PageBase {
|
||||||
width: parent.width - Tokens.padding.largeIncreased * 2
|
width: parent.width - Tokens.padding.largeIncreased * 2
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
AnimatedLogo {
|
Image {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: implicitWidth
|
Layout.preferredWidth: 104
|
||||||
Layout.preferredHeight: implicitHeight
|
Layout.preferredHeight: 104
|
||||||
|
source: Quickshell.shellDir + "/assets/nosignal-logo.png"
|
||||||
|
sourceSize.width: 208
|
||||||
|
sourceSize.height: 208
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Tokens.spacing.small
|
Layout.topMargin: Tokens.spacing.small
|
||||||
text: "Caelestia"
|
text: "NoSignal"
|
||||||
font: Tokens.font.headline.builders.large.width(110).build()
|
font: Tokens.font.headline.builders.large.width(110).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: CUtils.version ? `v${CUtils.version}` : "…"
|
text: "V1"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.body.medium
|
font: Tokens.font.body.medium
|
||||||
}
|
}
|
||||||
|
|
|
||||||
120
modules/nexus/pages/AdditionsPage.qml
Normal file
120
modules/nexus/pages/AdditionsPage.qml
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.components
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
// NoSignal: the Additions settings page (replaces the upstream Plugins
|
||||||
|
// placeholder). Optional software installed on demand from official sources
|
||||||
|
// (pacman repos / upstream installers — no AUR, no Flatpak). Items come from
|
||||||
|
// the additions.json manifest via the status cache written by
|
||||||
|
// nosignal-additions; Install runs in a visible floating terminal so
|
||||||
|
// git/sudo/pacman output and prompts stay in front of the user.
|
||||||
|
PageBase {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: qsTr("Additions")
|
||||||
|
|
||||||
|
property var status: ({})
|
||||||
|
readonly property var items: status.items || []
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
width: root.cappedWidth
|
||||||
|
spacing: Tokens.spacing.extraSmall / 2
|
||||||
|
|
||||||
|
// The Process objects live INSIDE the layout (its `data` accepts
|
||||||
|
// non-visual objects) — PageBase's default property is a single
|
||||||
|
// `Item`, so declaring them at page level kills the whole shell.
|
||||||
|
// Same pattern as UpdatesPage / the upstream AboutPage.
|
||||||
|
|
||||||
|
// Read the cached status (instant).
|
||||||
|
Process {
|
||||||
|
id: readProc
|
||||||
|
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", "cat \"${XDG_STATE_HOME:-$HOME/.local/state}/nosignal/additions-status.json\" 2>/dev/null"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
root.status = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
root.status = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the cache on demand (re-runs every item's check).
|
||||||
|
Process {
|
||||||
|
id: checkProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" status >/dev/null 2>&1"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run an installer in a visible floating terminal.
|
||||||
|
Process {
|
||||||
|
id: installProc
|
||||||
|
|
||||||
|
property string addId: ""
|
||||||
|
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" install " + addId + "; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Optional software")
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.items
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
first: index === 0
|
||||||
|
last: index === root.items.length - 1
|
||||||
|
icon: modelData.icon || "extension"
|
||||||
|
label: modelData.name
|
||||||
|
status: modelData.installed ? qsTr("Installed") : modelData.desc
|
||||||
|
onClicked: {
|
||||||
|
if (!modelData.installed && !installProc.running) {
|
||||||
|
installProc.addId = modelData.id;
|
||||||
|
installProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
visible: root.items.length === 0
|
||||||
|
first: true
|
||||||
|
last: true
|
||||||
|
label: qsTr("No additions manifest")
|
||||||
|
value: "—"
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Actions")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
first: true
|
||||||
|
last: true
|
||||||
|
icon: "refresh"
|
||||||
|
label: qsTr("Re-check installed state")
|
||||||
|
status: installProc.running ? qsTr("Install running in terminal…") : (checkProc.running ? qsTr("Checking…") : qsTr("Refreshes the list above"))
|
||||||
|
onClicked: {
|
||||||
|
if (!checkProc.running)
|
||||||
|
checkProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -97,9 +97,9 @@ PageBase {
|
||||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "tune"
|
icon: "tune"
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -123,10 +123,10 @@ PageBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "chevron_right"
|
icon: "chevron_right"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,13 @@ PageBase {
|
||||||
radius: Tokens.rounding.full
|
radius: Tokens.rounding.full
|
||||||
color: device.connected ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
color: device.connected ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: deviceIcon
|
id: deviceIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: Icons.getBluetoothIcon(device.modelData?.icon ?? "")
|
icon: Icons.getBluetoothIcon(device.modelData?.icon ?? "")
|
||||||
color: device.connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
color: device.connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
fill: device.connected ? 1 : 0
|
fill: device.connected ? 1 : 0
|
||||||
opacity: device.textOpacity
|
opacity: device.textOpacity
|
||||||
|
|
||||||
|
|
@ -193,9 +193,9 @@ PageBase {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "add"
|
icon: "add"
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
||||||
|
|
@ -92,41 +92,71 @@ PageBase {
|
||||||
text: qsTr("Weather")
|
text: qsTr("Weather")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder until the map-based location picker lands
|
// Location for the weather service. Weather.qml accepts a city name
|
||||||
|
// (geocoded) or a "lat,lon" string, and auto-detects via IP when empty.
|
||||||
ConnectedRect {
|
ConnectedRect {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
first: true
|
first: true
|
||||||
last: true
|
last: true
|
||||||
implicitHeight: comingSoon.implicitHeight + Tokens.padding.extraLarge * 2
|
implicitHeight: weatherLayout.implicitHeight + weatherLayout.anchors.margins * 2
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: comingSoon
|
id: weatherLayout
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
width: parent.width - Tokens.padding.largeIncreased * 2
|
anchors.margins: Tokens.padding.medium
|
||||||
spacing: Tokens.padding.extraSmall
|
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||||
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
MaterialIcon {
|
spacing: Tokens.spacing.small
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: "map"
|
|
||||||
color: Colours.palette.m3outlineVariant
|
|
||||||
font: Tokens.font.icon.extraLarge
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.fillWidth: true
|
||||||
text: qsTr("Location picker coming soon")
|
text: qsTr("Location")
|
||||||
color: Colours.palette.m3outlineVariant
|
font: Tokens.font.body.small
|
||||||
font: Tokens.font.title.small
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: locField.implicitHeight + Tokens.padding.medium * 2
|
||||||
|
|
||||||
|
radius: Tokens.rounding.small
|
||||||
|
color: Colours.tPalette.m3surfaceContainerLowest
|
||||||
|
border.color: Colours.palette.m3outlineVariant
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
onClicked: locField.focus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: locField
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Tokens.padding.large
|
||||||
|
anchors.rightMargin: Tokens.padding.large
|
||||||
|
|
||||||
|
text: GlobalConfig.services.weatherLocation
|
||||||
|
placeholderText: qsTr("City name or lat,long — empty = auto (IP)")
|
||||||
|
placeholderTextColor: Colours.palette.m3onSurfaceVariant
|
||||||
|
color: Colours.palette.m3onSurface
|
||||||
|
onEditingFinished: GlobalConfig.services.weatherLocation = text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text: qsTr("Choose your weather location on a map in a future update")
|
text: qsTr("Leave empty to detect your location automatically.")
|
||||||
color: Colours.palette.m3outlineVariant
|
color: Colours.palette.m3outline
|
||||||
font: Tokens.font.body.small
|
font: Tokens.font.label.small
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,10 @@ PageBase {
|
||||||
anchors.rightMargin: Tokens.padding.extraLarge
|
anchors.rightMargin: Tokens.padding.extraLarge
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: Icons.getNetworkIcon(network.modelData.strength)
|
icon: Icons.getNetworkIcon(network.modelData.strength)
|
||||||
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
opacity: network.textOpacity
|
opacity: network.textOpacity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,10 +165,10 @@ PageBase {
|
||||||
Component {
|
Component {
|
||||||
id: iconComp
|
id: iconComp
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: network.modelData.active ? "settings" : "lock"
|
icon: network.modelData.active ? "settings" : "lock"
|
||||||
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
opacity: network.textOpacity
|
opacity: network.textOpacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -218,9 +218,9 @@ PageBase {
|
||||||
|
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: "add"
|
icon: "add"
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,16 @@ PageBase {
|
||||||
onToggled: GlobalConfig.services.smartScheme = checked
|
onToggled: GlobalConfig.services.smartScheme = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoSignal: time-boxed passwordless sudo (15 min) toggle
|
||||||
|
SudoToggleRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoSignal: CachyOS repos enable/disable (sudoless, auto-tier)
|
||||||
|
CachyRepoToggleRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
SelectRow {
|
SelectRow {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
last: true
|
last: true
|
||||||
|
|
|
||||||
139
modules/nexus/pages/UpdatesPage.qml
Normal file
139
modules/nexus/pages/UpdatesPage.qml
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.components
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
// NoSignal: the Updates settings page (replaces the upstream placeholder).
|
||||||
|
// Reads the JSON cache written by nosignal-update-check; "Update now" runs
|
||||||
|
// nosignal-update in a floating terminal (sudo + pacman prompts stay visible).
|
||||||
|
PageBase {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: qsTr("Updates")
|
||||||
|
|
||||||
|
property var status: ({})
|
||||||
|
|
||||||
|
function count(v) {
|
||||||
|
return v === undefined ? "…" : String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
width: root.cappedWidth
|
||||||
|
spacing: Tokens.spacing.extraSmall / 2
|
||||||
|
|
||||||
|
// The Process objects live INSIDE the layout (its `data` accepts
|
||||||
|
// non-visual objects) — PageBase's default property is a single
|
||||||
|
// `Item`, so declaring them at page level kills the whole shell
|
||||||
|
// ("Cannot assign Process to QQuickItem*"). Same pattern as the
|
||||||
|
// upstream AboutPage.
|
||||||
|
|
||||||
|
// Read the cached status (instant; the user timer keeps it fresh).
|
||||||
|
Process {
|
||||||
|
id: readProc
|
||||||
|
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", "cat \"${XDG_STATE_HOME:-$HOME/.local/state}/nosignal/update-status.json\" 2>/dev/null"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
root.status = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
root.status = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the cache on demand.
|
||||||
|
Process {
|
||||||
|
id: checkProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "\"$HOME/.local/bin/nosignal-update-check\" >/dev/null 2>&1"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the real update in a visible floating terminal.
|
||||||
|
Process {
|
||||||
|
id: updateProc
|
||||||
|
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "\"$HOME/.local/bin/nosignal-update\"; \"$HOME/.local/bin/nosignal-update-check\" >/dev/null 2>&1; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Pending updates")
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
first: true
|
||||||
|
label: qsTr("Official packages")
|
||||||
|
value: root.count(root.status.repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
label: qsTr("AUR packages")
|
||||||
|
value: root.count(root.status.aur)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
label: qsTr("Flatpak")
|
||||||
|
value: root.count(root.status.flatpak)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
last: true
|
||||||
|
label: qsTr("NoSignal layer")
|
||||||
|
subtext: qsTr("Idempotent migrations applied by nosignal-update")
|
||||||
|
value: root.status.migrations_pending === undefined ? "…" : (root.status.migrations_pending > 0 ? qsTr("%1 migration(s) pending").arg(root.status.migrations_pending) : qsTr("up to date"))
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("History")
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
first: true
|
||||||
|
label: qsTr("Last full upgrade")
|
||||||
|
value: root.status.last_upgrade || "—"
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
last: true
|
||||||
|
label: qsTr("Last checked")
|
||||||
|
value: root.status.checked ? root.status.checked.replace("T", " ").substring(0, 16) : "—"
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Actions")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
first: true
|
||||||
|
icon: "refresh"
|
||||||
|
label: qsTr("Check for updates now")
|
||||||
|
status: checkProc.running ? qsTr("Checking…") : qsTr("Refreshes the counts above")
|
||||||
|
onClicked: {
|
||||||
|
if (!checkProc.running)
|
||||||
|
checkProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
last: true
|
||||||
|
icon: "system_update_alt"
|
||||||
|
label: qsTr("Update now")
|
||||||
|
status: updateProc.running ? qsTr("Running in terminal…") : qsTr("Opens a terminal: snapshot, packages, NoSignal layer")
|
||||||
|
onClicked: {
|
||||||
|
if (!updateProc.running)
|
||||||
|
updateProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,11 +46,11 @@ PageBase {
|
||||||
sourceComponent: ColumnLayout {
|
sourceComponent: ColumnLayout {
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "hide_image"
|
icon: "hide_image"
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,10 @@ PageBase {
|
||||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
text: Icons.getBluetoothIcon(newDevice.modelData?.icon ?? "")
|
icon: Icons.getBluetoothIcon(newDevice.modelData?.icon ?? "")
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
opacity: newDevice.textOpacity
|
opacity: newDevice.textOpacity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,11 @@ PageBase {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "delete"
|
icon: "delete"
|
||||||
color: forgetBtn.onColour
|
color: forgetBtn.onColour
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
@ -126,11 +126,11 @@ PageBase {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: root.connected ? "close" : "add"
|
icon: root.connected ? "close" : "add"
|
||||||
color: connectBtn.inactiveOnColour
|
color: connectBtn.inactiveOnColour
|
||||||
font: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
animate: true
|
animate: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ PageBase {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.padding.extraSmall
|
spacing: Tokens.padding.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "handyman"
|
icon: "handyman"
|
||||||
color: Colours.palette.m3outlineVariant
|
color: Colours.palette.m3outlineVariant
|
||||||
font: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
||||||
|
|
@ -168,9 +168,9 @@ PageBase {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "hide_image"
|
icon: "hide_image"
|
||||||
color: Colours.palette.m3outline
|
color: Colours.palette.m3outline
|
||||||
fontStyle: Tokens.font.icon.extraLarge
|
fontStyle: Tokens.font.icon.extraLarge
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Item {
|
||||||
anchors.rightMargin: root.clampedPadding
|
anchors.rightMargin: root.clampedPadding
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: Tokens.rounding.large
|
radius: 0
|
||||||
|
|
||||||
StyledListView {
|
StyledListView {
|
||||||
id: list
|
id: list
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ StyledRect {
|
||||||
property bool expanded: Config.notifs.openExpanded
|
property bool expanded: Config.notifs.openExpanded
|
||||||
|
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer
|
||||||
radius: Tokens.rounding.large
|
radius: 0
|
||||||
|
|
||||||
implicitHeight: inner.implicitHeight
|
implicitHeight: inner.implicitHeight
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ StyledRect {
|
||||||
visible: root.hasImage || root.hasAppIcon
|
visible: root.hasImage || root.hasAppIcon
|
||||||
|
|
||||||
sourceComponent: StyledClippingRect {
|
sourceComponent: StyledClippingRect {
|
||||||
radius: Tokens.rounding.full
|
radius: 0
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
|
||||||
implicitWidth: TokenConfig.sizes.notifs.image
|
implicitWidth: TokenConfig.sizes.notifs.image
|
||||||
implicitHeight: TokenConfig.sizes.notifs.image
|
implicitHeight: TokenConfig.sizes.notifs.image
|
||||||
|
|
@ -147,7 +147,7 @@ StyledRect {
|
||||||
anchors.bottom: root.hasImage ? image.bottom : undefined
|
anchors.bottom: root.hasImage ? image.bottom : undefined
|
||||||
|
|
||||||
sourceComponent: StyledRect {
|
sourceComponent: StyledRect {
|
||||||
radius: Tokens.rounding.full
|
radius: 0
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
|
||||||
implicitWidth: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
|
implicitWidth: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
|
||||||
implicitHeight: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
|
implicitHeight: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
|
||||||
|
|
@ -177,8 +177,8 @@ StyledRect {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenterOffset: 1
|
anchors.verticalCenterOffset: 1
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: NsIcon {
|
||||||
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
|
icon: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
}
|
}
|
||||||
|
|
@ -358,17 +358,17 @@ StyledRect {
|
||||||
implicitHeight: expandIcon.implicitHeight
|
implicitHeight: expandIcon.implicitHeight
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
radius: Tokens.rounding.full
|
radius: 0
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||||
onClicked: root.expanded = !root.expanded
|
onClicked: root.expanded = !root.expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
NsIcon {
|
||||||
id: expandIcon
|
id: expandIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenterOffset: root.expanded ? -1 : 1
|
anchors.verticalCenterOffset: root.expanded ? -1 : 1
|
||||||
text: "expand_more"
|
icon: "expand_more"
|
||||||
fontStyle: Tokens.font.icon.medium
|
fontStyle: Tokens.font.icon.medium
|
||||||
rotation: root.expanded ? 180 : 0
|
rotation: root.expanded ? 180 : 0
|
||||||
|
|
||||||
|
|
|
||||||
46
modules/nsbar/BarPill.qml
Normal file
46
modules/nsbar/BarPill.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal bar module pill: hover-highlightable (radius 5, hover bg); when its
|
||||||
|
// panel is open it uses accent.soft bg + accent foreground. Children go in a
|
||||||
|
// centered Row. `active` drives the open/highlight state; `triggered()` fires on
|
||||||
|
// click (the bar wires this to toggle its openPanel string).
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
property int hPad: 9
|
||||||
|
signal triggered
|
||||||
|
|
||||||
|
default property alias content: inner.data
|
||||||
|
|
||||||
|
implicitWidth: inner.implicitWidth + hPad * 2
|
||||||
|
implicitHeight: 26
|
||||||
|
radius: Theme.radius.barModule
|
||||||
|
color: active ? Theme.accentSoft : mouse.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.hoverMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: inner
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.triggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
260
modules/nsbar/NsBar.qml
Normal file
260
modules/nsbar/NsBar.qml
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal slim top bar (interface redesign 2026-06-14).
|
||||||
|
// Flat 38px translucent-glass PanelWindow at the top edge. Three zones:
|
||||||
|
// left : logo · | · workspaces · | · window-title
|
||||||
|
// center: clock
|
||||||
|
// right : media · bell · wifi · bt · volume · battery · | · power
|
||||||
|
// Each interactive module is a BarPill (hover bg; active = accent.soft + accent
|
||||||
|
// fg). `openPanel` is the single shell-level "one panel open at a time" string;
|
||||||
|
// clicking a pill toggles it. The popup panels themselves are built next — for
|
||||||
|
// now a click just highlights the pill. Reuses the existing service singletons.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
import qs.components
|
||||||
|
import qs.modules.bar.components as BC
|
||||||
|
import qs.modules.bar.components.workspaces as WS
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Screens.screens
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: win
|
||||||
|
|
||||||
|
required property ShellScreen modelData
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
color: "transparent"
|
||||||
|
WlrLayershell.namespace: "nsbar" // for Hyprland blur layerrule
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
|
||||||
|
anchors.top: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
|
||||||
|
implicitHeight: Theme.size.barHeight
|
||||||
|
exclusiveZone: Theme.size.barHeight
|
||||||
|
|
||||||
|
readonly property int hPad: 12
|
||||||
|
readonly property int gap: 6
|
||||||
|
|
||||||
|
// ── Glass surface + 1px bottom border ──────────────────────────────
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.barBg
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: 1
|
||||||
|
color: Theme.barBorder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Left zone ──────────────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: win.hPad
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: win.gap
|
||||||
|
|
||||||
|
BC.OsIcon {}
|
||||||
|
|
||||||
|
Divider {}
|
||||||
|
|
||||||
|
NsWorkspaces {}
|
||||||
|
|
||||||
|
Divider {}
|
||||||
|
|
||||||
|
BarPill {
|
||||||
|
id: titlePill
|
||||||
|
|
||||||
|
active: NsShell.open === "overview"
|
||||||
|
onTriggered: NsShell.toggle("overview", mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: "desktop_windows"
|
||||||
|
color: titlePill.active ? Theme.accent : Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: Hypr.activeToplevel?.title ?? "Desktop"
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: Math.min(implicitWidth, 220)
|
||||||
|
color: titlePill.active ? Theme.accent : Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Center zone: clock ─────────────────────────────────────────────
|
||||||
|
BarPill {
|
||||||
|
id: clockPill
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
active: NsShell.open === "calendar"
|
||||||
|
onTriggered: NsShell.toggle("calendar", mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
BC.Clock {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Right zone ─────────────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: win.hPad
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: win.gap
|
||||||
|
|
||||||
|
// Media / now-playing
|
||||||
|
BarPill {
|
||||||
|
id: mediaPill
|
||||||
|
|
||||||
|
visible: Players.active !== null
|
||||||
|
active: NsShell.open === "quicksettings"
|
||||||
|
onTriggered: NsShell.toggle("quicksettings", mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: Players.active?.isPlaying ? "pause" : "music_note"
|
||||||
|
color: mediaPill.active ? Theme.accent : Theme.green
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: Players.active?.trackTitle || "Awake"
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: Math.min(implicitWidth, 90)
|
||||||
|
color: mediaPill.active ? Theme.accent : Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System tray — only shown when an app actually registers a tray
|
||||||
|
// icon, otherwise the pill just opened an empty panel (F-T1).
|
||||||
|
IconPill {
|
||||||
|
icon: "widgets"
|
||||||
|
panel: "tray"
|
||||||
|
visible: SystemTray.items.values.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifications bell (with unread dot overlaid on the icon)
|
||||||
|
BarPill {
|
||||||
|
id: bellPill
|
||||||
|
|
||||||
|
active: NsShell.open === "notifications"
|
||||||
|
onTriggered: NsShell.toggle("notifications", mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: Notifs.notClosed.length > 0 ? "notifications" : "notifications_none"
|
||||||
|
color: bellPill.active ? Theme.accent : Theme.text
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: Notifs.notClosed.length > 0
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.rightMargin: -1
|
||||||
|
anchors.topMargin: 1
|
||||||
|
implicitWidth: 6
|
||||||
|
implicitHeight: 6
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wi-Fi
|
||||||
|
IconPill {
|
||||||
|
icon: Nmcli.active ? Icons.getNetworkIcon(Nmcli.active.strength ?? 0) : "wifi_off"
|
||||||
|
panel: "network"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bluetooth
|
||||||
|
IconPill {
|
||||||
|
icon: {
|
||||||
|
if (!Bluetooth.defaultAdapter?.enabled)
|
||||||
|
return "bluetooth_disabled";
|
||||||
|
if (Bluetooth.devices.values.some(d => d.connected))
|
||||||
|
return "bluetooth_connected";
|
||||||
|
return "bluetooth";
|
||||||
|
}
|
||||||
|
panel: "bluetooth"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
IconPill {
|
||||||
|
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
|
||||||
|
panel: "audio"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery / Quick Settings — always present (control-centre trigger):
|
||||||
|
// battery + % on laptops, a "tune" icon on desktops.
|
||||||
|
BarPill {
|
||||||
|
id: batteryPill
|
||||||
|
|
||||||
|
readonly property bool laptop: UPower.displayDevice.isLaptopBattery
|
||||||
|
active: NsShell.open === "quicksettings"
|
||||||
|
onTriggered: NsShell.toggle("quicksettings", mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: batteryPill.laptop ? Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state)) : "tune"
|
||||||
|
color: batteryPill.active ? Theme.accent : !batteryPill.laptop || !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? Theme.text : Theme.danger
|
||||||
|
fill: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: batteryPill.laptop
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
|
||||||
|
color: batteryPill.active ? Theme.accent : Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider {}
|
||||||
|
|
||||||
|
// Power
|
||||||
|
IconPill {
|
||||||
|
icon: "power_settings_new"
|
||||||
|
panel: "power"
|
||||||
|
iconColour: Theme.danger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reusable bits ──────────────────────────────────────────────────
|
||||||
|
component Divider: Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
implicitWidth: 1
|
||||||
|
implicitHeight: 16
|
||||||
|
color: Theme.barBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon-only pill bound to the shell openPanel state
|
||||||
|
component IconPill: BarPill {
|
||||||
|
id: p
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property string panel
|
||||||
|
property color iconColour: Theme.text
|
||||||
|
|
||||||
|
active: NsShell.open === panel
|
||||||
|
onTriggered: NsShell.toggle(panel, mapToItem(null, width / 2, 0).x, win.modelData.name)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: p.icon
|
||||||
|
color: p.active ? Theme.accent : p.iconColour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
modules/nsbar/NsOverlay.qml
Normal file
166
modules/nsbar/NsOverlay.qml
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal popup overlay: one full-screen layer per screen that hosts the active
|
||||||
|
// floating panel (driven by NsShell.open). A transparent click-catcher behind the
|
||||||
|
// panel closes on outside-click; Escape also closes. Panels are positioned below
|
||||||
|
// the bar, anchored center / right / under-trigger per their `anchorMode`.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.services
|
||||||
|
import "panels" as Panels
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Screens.screens
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: ov
|
||||||
|
|
||||||
|
required property ShellScreen modelData
|
||||||
|
|
||||||
|
// This overlay owns the panel only when NsShell.screen names this output
|
||||||
|
// (or is "" = unscoped, e.g. the global power modal → show on all screens).
|
||||||
|
// Without this, every screen's overlay rendered the panel → mirrored popups.
|
||||||
|
readonly property bool onThisScreen: NsShell.screen === "" || NsShell.screen === modelData.name
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
visible: NsShell.open !== "" && ov.onThisScreen
|
||||||
|
color: "transparent"
|
||||||
|
WlrLayershell.namespace: "nspanels"
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
anchors.top: true
|
||||||
|
anchors.bottom: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
exclusiveZone: 0
|
||||||
|
|
||||||
|
// outside-click closes
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: NsShell.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape closes
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: ov.visible
|
||||||
|
Keys.onEscapePressed: NsShell.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// full-screen modal: Power Menu
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: NsShell.open === "power" && ov.onThisScreen
|
||||||
|
sourceComponent: powerC
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: powerC
|
||||||
|
|
||||||
|
Panels.NsPowerMenu {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
active: NsShell.open !== "" && NsShell.open !== "power" && ov.onThisScreen
|
||||||
|
y: Theme.size.barHeight + 6
|
||||||
|
x: {
|
||||||
|
if (!item)
|
||||||
|
return 0;
|
||||||
|
const w = item.implicitWidth;
|
||||||
|
const mode = item.anchorMode ?? "right";
|
||||||
|
if (mode === "center")
|
||||||
|
return Math.round((ov.width - w) / 2);
|
||||||
|
if (mode === "trigger")
|
||||||
|
return Math.max(8, Math.min(ov.width - w - 8, NsShell.anchorX - w / 2));
|
||||||
|
return ov.width - w - 12; // right
|
||||||
|
}
|
||||||
|
|
||||||
|
// small enter slide
|
||||||
|
opacity: active ? 1 : 0
|
||||||
|
transform: Translate {
|
||||||
|
y: loader.active ? 0 : -5
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: {
|
||||||
|
switch (NsShell.open) {
|
||||||
|
case "calendar":
|
||||||
|
return calendarC;
|
||||||
|
case "quicksettings":
|
||||||
|
return quickSettingsC;
|
||||||
|
case "network":
|
||||||
|
return networkC;
|
||||||
|
case "bluetooth":
|
||||||
|
return bluetoothC;
|
||||||
|
case "notifications":
|
||||||
|
return notificationsC;
|
||||||
|
case "audio":
|
||||||
|
return audioC;
|
||||||
|
case "tray":
|
||||||
|
return trayC;
|
||||||
|
case "overview":
|
||||||
|
return overviewC;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: calendarC
|
||||||
|
|
||||||
|
Panels.NsCalendar {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: quickSettingsC
|
||||||
|
|
||||||
|
Panels.NsQuickSettings {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: networkC
|
||||||
|
|
||||||
|
Panels.NsNetwork {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: bluetoothC
|
||||||
|
|
||||||
|
Panels.NsBluetooth {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notificationsC
|
||||||
|
|
||||||
|
Panels.NsNotifications {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioC
|
||||||
|
|
||||||
|
Panels.NsAudio {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: trayC
|
||||||
|
|
||||||
|
Panels.NsSystemTray {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: overviewC
|
||||||
|
|
||||||
|
Panels.NsOverview {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
modules/nsbar/NsWorkspaces.qml
Normal file
85
modules/nsbar/NsWorkspaces.qml
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal workspaces module: numbered 1..N, no capsule. Active = filled accent
|
||||||
|
// pill with dark (onAccent) text + workspace name (e.g. "2 code"); occupied =
|
||||||
|
// bright number; empty = faint number. Click switches workspace.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Config.bar.workspaces.shown
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: ws
|
||||||
|
|
||||||
|
required property int index
|
||||||
|
readonly property int wsId: index + 1
|
||||||
|
readonly property var hw: Hypr.workspaces.values.find(w => w.id === wsId) ?? null
|
||||||
|
readonly property bool active: Hypr.activeWsId === wsId
|
||||||
|
readonly property bool occupied: (hw?.lastIpcObject.windows ?? 0) > 0
|
||||||
|
readonly property string wsName: {
|
||||||
|
const n = hw?.name ?? "";
|
||||||
|
return n && n !== String(wsId) && !n.startsWith("special") ? n : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
implicitWidth: row.implicitWidth + (active ? 16 : 10)
|
||||||
|
implicitHeight: 22
|
||||||
|
radius: Theme.radius.barModule
|
||||||
|
color: active ? Theme.accent : mouse.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.hoverMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 120
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: ws.wsId
|
||||||
|
color: ws.active ? Theme.onAccent : ws.occupied ? Theme.text : Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
font.weight: ws.active ? Font.DemiBold : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: ws.active && ws.wsName !== ""
|
||||||
|
text: ws.wsName
|
||||||
|
color: Theme.onAccent
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Hypr.dispatch(`workspace ${ws.wsId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
modules/nsbar/panels/NsAudio.qml
Normal file
162
modules/nsbar/panels/NsAudio.qml
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Audio Mixer popout (off the volume pill). Output device row, master
|
||||||
|
// slider, per-app application sliders, input (mic) slider. Wired to Pipewire
|
||||||
|
// via the Audio service.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 340
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
// output device
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: "speaker"
|
||||||
|
color: Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Audio.sink?.description || Audio.sink?.name || "Output"
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: "expand_more"
|
||||||
|
color: Theme.textFaint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSlider {
|
||||||
|
icon: Audio.muted ? "volume_off" : "volume_up"
|
||||||
|
value: Audio.volume
|
||||||
|
onMoved: v => Audio.setVolume(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applications
|
||||||
|
StyledText {
|
||||||
|
visible: Audio.streams.length > 0
|
||||||
|
text: "APPLICATIONS"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.label
|
||||||
|
font.letterSpacing: Theme.font.trackingLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Audio.streams
|
||||||
|
|
||||||
|
delegate: AudioSlider {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
icon: "graphic_eq"
|
||||||
|
label: modelData?.properties?.["application.name"] || modelData?.description || modelData?.name || "App"
|
||||||
|
value: modelData?.audio?.volume ?? 0
|
||||||
|
onMoved: v => {
|
||||||
|
if (modelData?.audio)
|
||||||
|
modelData.audio.volume = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// input
|
||||||
|
StyledText {
|
||||||
|
text: "INPUT"
|
||||||
|
Layout.topMargin: 2
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.label
|
||||||
|
font.letterSpacing: Theme.font.trackingLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSlider {
|
||||||
|
icon: Audio.sourceMuted ? "mic_off" : "mic"
|
||||||
|
value: Audio.sourceVolume
|
||||||
|
onMoved: v => Audio.setSourceVolume(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
component AudioSlider: ColumnLayout {
|
||||||
|
id: s
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property string label: ""
|
||||||
|
property real value: 0
|
||||||
|
signal moved(real v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: s.label !== ""
|
||||||
|
text: s.label
|
||||||
|
color: Theme.textMuted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: s.icon
|
||||||
|
color: Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 6
|
||||||
|
radius: 0
|
||||||
|
color: Theme.fillSubtle
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * Math.max(0, Math.min(1, s.value))
|
||||||
|
height: parent.height
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: parent.width * Math.max(0, Math.min(1, s.value)) - width / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
implicitWidth: 14
|
||||||
|
implicitHeight: 14
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onPressed: e => s.moved(Math.max(0, Math.min(1, e.x / width)))
|
||||||
|
onPositionChanged: e => {
|
||||||
|
if (pressed)
|
||||||
|
s.moved(Math.max(0, Math.min(1, e.x / width)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: Math.round(s.value * 100) + "%"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
modules/nsbar/panels/NsBluetooth.qml
Normal file
150
modules/nsbar/panels/NsBluetooth.qml
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Bluetooth popout (off the BT pill). Title + switch; device rows (icon
|
||||||
|
// tile peach-tinted if connected, name, status, battery %). Footer "Bluetooth
|
||||||
|
// settings". Wired to Quickshell.Bluetooth (BlueZ).
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 340
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
readonly property var adapter: Bluetooth.defaultAdapter
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Bluetooth"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NsSwitch {
|
||||||
|
on: root.adapter?.enabled ?? false
|
||||||
|
onToggled: if (root.adapter)
|
||||||
|
root.adapter.enabled = !root.adapter.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
visible: root.adapter?.enabled ?? false
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Bluetooth.devices
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
required property BluetoothDevice modelData
|
||||||
|
readonly property bool connected: modelData?.connected ?? false
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 44
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: ma.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 30
|
||||||
|
implicitHeight: 30
|
||||||
|
radius: 0
|
||||||
|
color: row.connected ? Theme.accentSoft : Theme.fillSubtle
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: Icons.getBluetoothIcon(row.modelData?.icon ?? "")
|
||||||
|
color: row.connected ? Theme.accent : Theme.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: row.modelData?.name ?? "?"
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: row.connected ? "Connected" : (row.modelData?.paired ?? false) ? "Paired" : "Not connected"
|
||||||
|
color: row.connected ? Theme.accent : Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: row.connected && (row.modelData?.battery ?? 0) > 0
|
||||||
|
text: Math.round((row.modelData?.battery ?? 0) * 100) + "%"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ma
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: Bluetooth.devices.values.length === 0
|
||||||
|
text: "No devices"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: !(root.adapter?.enabled ?? false)
|
||||||
|
text: "Bluetooth is off"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 4
|
||||||
|
implicitHeight: 1
|
||||||
|
color: Theme.barBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Bluetooth settings"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
175
modules/nsbar/panels/NsCalendar.qml
Normal file
175
modules/nsbar/panels/NsCalendar.qml
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Calendar popout (off the center clock). Month grid (Mon-first),
|
||||||
|
// today = filled accent circle. Below a divider, a TODAY events section
|
||||||
|
// (empty-state for now; real events can be wired from ICS/khal later).
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 304
|
||||||
|
anchorMode: "center"
|
||||||
|
|
||||||
|
property int monthOffset: 0
|
||||||
|
readonly property date base: new Date(Time.date.getFullYear(), Time.date.getMonth() + monthOffset, 1)
|
||||||
|
readonly property int year: base.getFullYear()
|
||||||
|
readonly property int month: base.getMonth()
|
||||||
|
readonly property bool isCurrentMonth: monthOffset === 0
|
||||||
|
readonly property int todayDate: Time.date.getDate()
|
||||||
|
|
||||||
|
readonly property var cells: {
|
||||||
|
const first = new Date(year, month, 1);
|
||||||
|
const lead = (first.getDay() + 6) % 7; // Monday-first
|
||||||
|
const dim = new Date(year, month + 1, 0).getDate();
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < lead; i++)
|
||||||
|
arr.push(0);
|
||||||
|
for (let d = 1; d <= dim; d++)
|
||||||
|
arr.push(d);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Header: month + year, prev/next ──────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.base.toLocaleString(Qt.locale(), "MMMM")
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.year
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NavBtn {
|
||||||
|
icon: "chevron_left"
|
||||||
|
onActivated: root.monthOffset--
|
||||||
|
}
|
||||||
|
|
||||||
|
NavBtn {
|
||||||
|
icon: "chevron_right"
|
||||||
|
onActivated: root.monthOffset++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Grid: weekday header + days ──────────────────────────────────────
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 7
|
||||||
|
rowSpacing: 2
|
||||||
|
columnSpacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: modelData
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.cells
|
||||||
|
|
||||||
|
Item {
|
||||||
|
required property int modelData
|
||||||
|
readonly property bool today: root.isCurrentMonth && modelData === root.todayDate
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 30
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: parent.modelData > 0
|
||||||
|
implicitWidth: 26
|
||||||
|
implicitHeight: 26
|
||||||
|
radius: 0
|
||||||
|
color: parent.today ? Theme.accent : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: parent.parent.modelData > 0 ? parent.parent.modelData : ""
|
||||||
|
color: parent.parent.today ? Theme.onAccent : Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Divider ──────────────────────────────────────────────────────────
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 4
|
||||||
|
implicitHeight: 1
|
||||||
|
color: Theme.barBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TODAY events ─────────────────────────────────────────────────────
|
||||||
|
StyledText {
|
||||||
|
text: "TODAY"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.label
|
||||||
|
font.letterSpacing: Theme.font.trackingLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "No events"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
component NavBtn: Rectangle {
|
||||||
|
property string icon
|
||||||
|
signal activated
|
||||||
|
|
||||||
|
implicitWidth: 24
|
||||||
|
implicitHeight: 24
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: ma.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: parent.icon
|
||||||
|
color: Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ma
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: parent.activated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
237
modules/nsbar/panels/NsNetwork.qml
Normal file
237
modules/nsbar/panels/NsNetwork.qml
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Network popout (off the Wi-Fi pill). Title + switch; list of networks
|
||||||
|
// (signal glyph accent if connected, SSID, Connected label, lock if secured);
|
||||||
|
// connected row = accent.soft. Footer "Network settings". Wired to Network/Nmcli.
|
||||||
|
//
|
||||||
|
// FIX 2026-06-20 (hardware test): the shipped panel was display-only — the row
|
||||||
|
// MouseArea had no onClicked and there was no password field, so a secured Wi-Fi
|
||||||
|
// network could not be joined from the GUI (user had to drop to nmcli/nmtui).
|
||||||
|
// This wires the existing Network service: click a row to connect; open or
|
||||||
|
// already-saved networks connect directly; a secured network with no saved
|
||||||
|
// profile expands an inline password field (StyledTextField, echoMode Password).
|
||||||
|
// NsOverlay grants OnDemand keyboard focus while visible, so the field is typable.
|
||||||
|
// NOTE: authored on the test machine but NOT yet built/cert'd — review in builder.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
import qs.components
|
||||||
|
import qs.components.controls
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 340
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
// ssid of the secured row currently showing its inline password field ("" = none)
|
||||||
|
property string expandedSsid: ""
|
||||||
|
|
||||||
|
// Decide what a click on a network row does.
|
||||||
|
function activate(m): void {
|
||||||
|
if (!m)
|
||||||
|
return;
|
||||||
|
if (m.active) {
|
||||||
|
// Already connected -> tapping disconnects.
|
||||||
|
Network.disconnectFromNetwork();
|
||||||
|
root.expandedSsid = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m.isSecure || Network.hasSavedProfile(m.ssid)) {
|
||||||
|
// Open network, or we already hold the PSK -> connect straight away.
|
||||||
|
Network.connectToNetworkWithPasswordCheck(m.ssid, m.isSecure, () => {}, m.bssid);
|
||||||
|
root.expandedSsid = "";
|
||||||
|
} else {
|
||||||
|
// Secured + no saved profile -> reveal the inline password field.
|
||||||
|
root.expandedSsid = (root.expandedSsid === m.ssid) ? "" : m.ssid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Wi-Fi"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NsSwitch {
|
||||||
|
on: Network.wifiEnabled
|
||||||
|
onToggled: Network.toggleWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// network list
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
visible: Network.wifiEnabled
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Network.networks
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
id: rowWrap
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
readonly property bool connected: modelData?.active ?? false
|
||||||
|
readonly property bool expanded: root.expandedSsid === (modelData?.ssid ?? " ")
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
function doConnect(): void {
|
||||||
|
const m = rowWrap.modelData;
|
||||||
|
if (!m || pwField.text.length === 0)
|
||||||
|
return;
|
||||||
|
Network.connectToNetwork(m.ssid, pwField.text, m.bssid, () => {});
|
||||||
|
pwField.text = "";
|
||||||
|
root.expandedSsid = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: rowWrap.connected ? Theme.accentSoft : ma.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: Icons.getNetworkIcon(rowWrap.modelData?.strength ?? 0)
|
||||||
|
color: rowWrap.connected ? Theme.accent : Theme.text
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: rowWrap.modelData?.ssid ?? "?"
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
visible: rowWrap.connected
|
||||||
|
text: "Connected"
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
visible: rowWrap.modelData?.isSecure ?? false
|
||||||
|
icon: "lock"
|
||||||
|
color: Theme.textFaint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ma
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.activate(rowWrap.modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// inline password entry — only for a secured network with no saved profile
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
Layout.bottomMargin: 4
|
||||||
|
spacing: 8
|
||||||
|
visible: rowWrap.expanded
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible)
|
||||||
|
pwField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: pwField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
echoMode: TextField.Password
|
||||||
|
placeholderText: "Password"
|
||||||
|
onAccepted: rowWrap.doConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 72
|
||||||
|
implicitHeight: 28
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: cma.containsMouse ? Theme.accent : Theme.accentSoft
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Connect"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cma
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: rowWrap.doConnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: Network.networks.length === 0
|
||||||
|
text: "No networks found"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: !Network.wifiEnabled
|
||||||
|
text: "Wi-Fi is off"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
// footer
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 4
|
||||||
|
implicitHeight: 1
|
||||||
|
color: Theme.barBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Network settings"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
179
modules/nsbar/panels/NsNotifications.qml
Normal file
179
modules/nsbar/panels/NsNotifications.qml
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Notifications popout (off the bell). Header (count + DND + Clear),
|
||||||
|
// scrollable cards (tinted icon, title, body, time), empty state. Uses Notifs.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 380
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Notifications"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "· " + Notifs.notClosed.length + " new"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 26
|
||||||
|
implicitHeight: 26
|
||||||
|
radius: 0
|
||||||
|
color: Notifs.dnd ? Theme.accentSoft : dndMa.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: Notifs.dnd ? "do_not_disturb_on" : "dark_mode"
|
||||||
|
color: Notifs.dnd ? Theme.accent : Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dndMa
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Notifs.dnd = !Notifs.dnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Clear"
|
||||||
|
color: clearMa.containsMouse ? Theme.accent : Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: clearMa
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -6
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
// Mirror the service's own clear() / clearNotifs shortcut:
|
||||||
|
// NotifData.close() sets `closed` (drops it from notClosed so the
|
||||||
|
// card disappears), removes it from Notifs.list, dismisses the
|
||||||
|
// server notification and destroys it. Calling
|
||||||
|
// `notification?.dismiss()` directly cleared nothing — it never
|
||||||
|
// touched the list, and is a no-op for notifications restored from
|
||||||
|
// disk (their `notification` is null). Iterate a slice() copy so
|
||||||
|
// mutating the list mid-loop is safe.
|
||||||
|
for (const n of Notifs.list.slice())
|
||||||
|
n.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cards
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Notifs.notClosed
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: card
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: cardRow.implicitHeight + 20
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: Theme.fillSubtle
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: cardRow
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
implicitWidth: 30
|
||||||
|
implicitHeight: 30
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accentSoft
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: Icons.getNotifIcon(card.modelData?.summary ?? "", card.modelData?.urgency ?? 1)
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: card.modelData?.summary ?? ""
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: card.modelData?.timeStr ?? ""
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: text !== ""
|
||||||
|
text: card.modelData?.body ?? ""
|
||||||
|
color: Theme.textMuted
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 3
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: Notifs.notClosed.length === 0
|
||||||
|
Layout.topMargin: 6
|
||||||
|
text: "No notifications"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
modules/nsbar/panels/NsOverview.qml
Normal file
113
modules/nsbar/panels/NsOverview.qml
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Workspaces overview (off the window-title module). Row of workspace
|
||||||
|
// thumbnails (active = accent border + glow), number + name below, click to
|
||||||
|
// switch. Wired to Hypr.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 5 * 124 + 16 * 2 + Theme.size.panelPad * 2 - 16
|
||||||
|
anchorMode: "center"
|
||||||
|
|
||||||
|
readonly property int shown: Config.bar.workspaces.shown
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Workspaces"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.shown + " desktops"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.shown
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
id: cell
|
||||||
|
|
||||||
|
required property int index
|
||||||
|
readonly property int wsId: index + 1
|
||||||
|
readonly property var hw: Hypr.workspaces.values.find(w => w.id === wsId) ?? null
|
||||||
|
readonly property bool active: Hypr.activeWsId === wsId
|
||||||
|
readonly property bool occupied: (hw?.lastIpcObject.windows ?? 0) > 0
|
||||||
|
readonly property string wsName: {
|
||||||
|
const n = hw?.name ?? "";
|
||||||
|
return n && n !== String(wsId) && !n.startsWith("special") ? n : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 108
|
||||||
|
implicitHeight: 68 // 16:10-ish
|
||||||
|
radius: Theme.radius.tile
|
||||||
|
color: Qt.rgba(0, 0, 0, cell.occupied ? 0.35 : 0.5)
|
||||||
|
border.width: cell.active ? 2 : 1
|
||||||
|
border.color: cell.active ? Theme.accent : Theme.panelBorder
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: cell.occupied
|
||||||
|
icon: "web_asset"
|
||||||
|
color: Theme.textFaint
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
Hypr.dispatch(`workspace ${cell.wsId}`);
|
||||||
|
NsShell.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: cell.wsId
|
||||||
|
color: cell.active ? Theme.accent : Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
font.weight: cell.active ? Font.DemiBold : Font.Normal
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
visible: cell.wsName !== ""
|
||||||
|
text: cell.wsName
|
||||||
|
color: cell.active ? Theme.accent : Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
modules/nsbar/panels/NsPowerMenu.qml
Normal file
143
modules/nsbar/panels/NsPowerMenu.qml
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Power Menu — full-screen modal overlay. Big clock + date, then 5
|
||||||
|
// action tiles (Lock, Suspend, Log Out, Reboot, Shut Down=danger). Esc / click
|
||||||
|
// scrim cancels. Rendered full-screen by NsOverlay (open === "power").
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function run(cmd: string): void {
|
||||||
|
Quickshell.execDetached(["sh", "-c", cmd]);
|
||||||
|
NsShell.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// scrim
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.overlayBg
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: NsShell.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 28
|
||||||
|
|
||||||
|
// big clock + date
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: Time.format("hh:mm")
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.clockBig
|
||||||
|
font.weight: Font.Light
|
||||||
|
font.letterSpacing: Theme.font.trackingClockBig
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: Time.format("dddd, d MMMM")
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// action tiles
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
Tile {
|
||||||
|
icon: "lock"
|
||||||
|
label: "Lock"
|
||||||
|
onActivated: root.run("loginctl lock-session")
|
||||||
|
}
|
||||||
|
Tile {
|
||||||
|
icon: "bedtime"
|
||||||
|
label: "Suspend"
|
||||||
|
onActivated: root.run("systemctl suspend")
|
||||||
|
}
|
||||||
|
Tile {
|
||||||
|
icon: "logout"
|
||||||
|
label: "Log Out"
|
||||||
|
onActivated: root.run("hyprctl dispatch exit")
|
||||||
|
}
|
||||||
|
Tile {
|
||||||
|
icon: "restart_alt"
|
||||||
|
label: "Reboot"
|
||||||
|
onActivated: root.run("systemctl reboot")
|
||||||
|
}
|
||||||
|
Tile {
|
||||||
|
icon: "power_settings_new"
|
||||||
|
label: "Shut Down"
|
||||||
|
danger: true
|
||||||
|
onActivated: root.run("systemctl poweroff")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: "Press Esc to cancel"
|
||||||
|
color: Theme.textFaint
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Tile: Rectangle {
|
||||||
|
id: tile
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property string label
|
||||||
|
property bool danger: false
|
||||||
|
signal activated
|
||||||
|
|
||||||
|
implicitWidth: 96
|
||||||
|
implicitHeight: 96
|
||||||
|
radius: Theme.radius.panel
|
||||||
|
color: ma.containsMouse ? Theme.accentSoft : Theme.panelBg
|
||||||
|
border.width: 1
|
||||||
|
border.color: ma.containsMouse ? (tile.danger ? Theme.danger : Theme.accent) : Theme.panelBorder
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
icon: tile.icon
|
||||||
|
color: tile.danger ? Theme.danger : Theme.text
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: tile.label
|
||||||
|
color: tile.danger ? Theme.danger : Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ma
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: tile.activated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
424
modules/nsbar/panels/NsQuickSettings.qml
Normal file
424
modules/nsbar/panels/NsQuickSettings.qml
Normal file
|
|
@ -0,0 +1,424 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal Quick Settings popout (off the media / battery pill). Avatar + user +
|
||||||
|
// uptime + power button; 2-col toggle grid; brightness + volume sliders; media
|
||||||
|
// card. Wired to the real services (Network, Bluetooth, Notifs, Audio,
|
||||||
|
// Brightness, Players). Night Light drives hyprsunset; Airplane drives the
|
||||||
|
// NetworkManager + Bluetooth radios. (VPN tile removed pending a real backend.)
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import qs.services
|
||||||
|
import qs.utils
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 380
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
readonly property var brightMon: Brightness.monitors[0] ?? null
|
||||||
|
|
||||||
|
// Night Light state tracks the real hyprsunset process rather than a local
|
||||||
|
// flag, so the tile stays correct across shell reloads or changes made
|
||||||
|
// elsewhere. Seeded on load (probe runs immediately), reconciled on a timer,
|
||||||
|
// and re-checked right after a toggle.
|
||||||
|
property bool nightLightOn: false
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: nightProbe
|
||||||
|
command: ["pgrep", "-x", "hyprsunset"]
|
||||||
|
running: true
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.nightLightOn = text.trim().length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 3000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: nightProbe.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: nightRefresh
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
onTriggered: nightProbe.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Header ───────────────────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 40
|
||||||
|
implicitHeight: 40
|
||||||
|
radius: 0
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Vertical
|
||||||
|
GradientStop {
|
||||||
|
position: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: Theme.blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: (SysInfo.user || "?").charAt(0).toUpperCase()
|
||||||
|
color: Theme.onAccent
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.body
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: SysInfo.user || "user"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: "uptime " + SysInfo.uptime
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 34
|
||||||
|
implicitHeight: 34
|
||||||
|
radius: 0
|
||||||
|
color: pwrMa.containsMouse ? Theme.accentSoft : Theme.fillSubtle
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: "power_settings_new"
|
||||||
|
color: Theme.danger
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pwrMa
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: NsShell.show("power", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toggle grid ──────────────────────────────────────────────────────
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: 8
|
||||||
|
columnSpacing: 8
|
||||||
|
|
||||||
|
QSToggle {
|
||||||
|
icon: "wifi"
|
||||||
|
label: "Wi-Fi"
|
||||||
|
sub: Network.wifiEnabled ? Network.active?.ssid ?? "On" : "Off"
|
||||||
|
on: Network.wifiEnabled
|
||||||
|
onToggled: Network.toggleWifi()
|
||||||
|
}
|
||||||
|
QSToggle {
|
||||||
|
icon: "bluetooth"
|
||||||
|
label: "Bluetooth"
|
||||||
|
sub: Bluetooth.defaultAdapter?.enabled ? "On" : "Off"
|
||||||
|
on: Bluetooth.defaultAdapter?.enabled ?? false
|
||||||
|
onToggled: if (Bluetooth.defaultAdapter)
|
||||||
|
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter.enabled
|
||||||
|
}
|
||||||
|
QSToggle {
|
||||||
|
icon: "do_not_disturb_on"
|
||||||
|
label: "Do Not Disturb"
|
||||||
|
sub: Notifs.dnd ? "On" : "Off"
|
||||||
|
on: Notifs.dnd
|
||||||
|
onToggled: Notifs.dnd = !Notifs.dnd
|
||||||
|
}
|
||||||
|
QSToggle {
|
||||||
|
id: nightTog
|
||||||
|
icon: "nightlight"
|
||||||
|
label: "Night Light"
|
||||||
|
sub: on ? "Warm 3500K" : "Off"
|
||||||
|
on: root.nightLightOn
|
||||||
|
onToggled: {
|
||||||
|
if (root.nightLightOn)
|
||||||
|
Quickshell.execDetached(["pkill", "-x", "hyprsunset"]);
|
||||||
|
else
|
||||||
|
Quickshell.execDetached(["sh", "-c", "pkill -x hyprsunset; hyprsunset -t 3500"]);
|
||||||
|
nightRefresh.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QSToggle {
|
||||||
|
id: airTog
|
||||||
|
icon: "airplanemode_active"
|
||||||
|
label: "Airplane"
|
||||||
|
// Reflects the real radio state: airplane is "On" when both Wi-Fi and
|
||||||
|
// Bluetooth are down. Network.wifiEnabled tracks nmcli; the adapter
|
||||||
|
// tracks BlueZ, so the tile self-corrects if radios change elsewhere.
|
||||||
|
readonly property bool radiosOff: !Network.wifiEnabled && !(Bluetooth.defaultAdapter?.enabled ?? false)
|
||||||
|
sub: radiosOff ? "On" : "Off"
|
||||||
|
on: radiosOff
|
||||||
|
onToggled: {
|
||||||
|
if (radiosOff)
|
||||||
|
Quickshell.execDetached(["sh", "-c", "nmcli radio all on; bluetoothctl power on"]);
|
||||||
|
else
|
||||||
|
Quickshell.execDetached(["sh", "-c", "nmcli radio all off; bluetoothctl power off"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sliders ──────────────────────────────────────────────────────────
|
||||||
|
QSSlider {
|
||||||
|
icon: "brightness_6"
|
||||||
|
value: root.brightMon?.brightness ?? 0
|
||||||
|
visible: root.brightMon !== null
|
||||||
|
onMoved: v => root.brightMon?.setBrightness(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
QSSlider {
|
||||||
|
icon: Audio.muted ? "volume_off" : "volume_up"
|
||||||
|
value: Audio.volume
|
||||||
|
onMoved: v => Audio.setVolume(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Media card ───────────────────────────────────────────────────────
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 4
|
||||||
|
implicitHeight: 80
|
||||||
|
radius: Theme.radius.tile
|
||||||
|
color: Theme.fillSubtle
|
||||||
|
visible: Players.active !== null
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 56
|
||||||
|
implicitHeight: 56
|
||||||
|
radius: 0
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Vertical
|
||||||
|
GradientStop {
|
||||||
|
position: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: Theme.blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: "music_note"
|
||||||
|
color: Theme.onAccent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Players.active?.trackTitle || "Awake"
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.body
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Players.active?.trackArtist || ""
|
||||||
|
color: Theme.textMuted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 16
|
||||||
|
Layout.topMargin: 2
|
||||||
|
|
||||||
|
MediaBtn {
|
||||||
|
icon: "skip_previous"
|
||||||
|
onActivated: Players.previous()
|
||||||
|
}
|
||||||
|
MediaBtn {
|
||||||
|
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||||
|
onActivated: Players.togglePlaying()
|
||||||
|
}
|
||||||
|
MediaBtn {
|
||||||
|
icon: "skip_next"
|
||||||
|
onActivated: Players.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reusable bits ────────────────────────────────────────────────────
|
||||||
|
component QSToggle: Rectangle {
|
||||||
|
id: tile
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property string label
|
||||||
|
property string sub
|
||||||
|
property bool on: false
|
||||||
|
signal toggled
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 56
|
||||||
|
radius: Theme.radius.button
|
||||||
|
color: tile.on ? Theme.accentSoft : Theme.fillSubtle
|
||||||
|
border.width: tile.on ? 1 : 0
|
||||||
|
border.color: Theme.accent
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 11
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 30
|
||||||
|
implicitHeight: 30
|
||||||
|
radius: 0
|
||||||
|
color: tile.on ? Theme.accent : Qt.rgba(1, 1, 1, 0.06)
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: tile.icon
|
||||||
|
color: tile.on ? Theme.onAccent : Theme.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: tile.label
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: tile.sub
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: tile.toggled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component QSSlider: RowLayout {
|
||||||
|
id: s
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property real value: 0
|
||||||
|
signal moved(real v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
icon: s.icon
|
||||||
|
color: Theme.textMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: track
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 6
|
||||||
|
radius: 0
|
||||||
|
color: Theme.fillSubtle
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * Math.max(0, Math.min(1, s.value))
|
||||||
|
height: parent.height
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: parent.width * Math.max(0, Math.min(1, s.value)) - width / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
implicitWidth: 14
|
||||||
|
implicitHeight: 14
|
||||||
|
radius: 0
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onPressed: e => s.moved(Math.max(0, Math.min(1, e.x / width)))
|
||||||
|
onPositionChanged: e => {
|
||||||
|
if (pressed)
|
||||||
|
s.moved(Math.max(0, Math.min(1, e.x / width)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: Math.round(s.value * 100) + "%"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component MediaBtn: Rectangle {
|
||||||
|
property string icon
|
||||||
|
signal activated
|
||||||
|
|
||||||
|
implicitWidth: 28
|
||||||
|
implicitHeight: 28
|
||||||
|
radius: 0
|
||||||
|
color: mbm.containsMouse ? Theme.hover : "transparent"
|
||||||
|
|
||||||
|
NsIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
icon: parent.icon
|
||||||
|
color: Theme.text
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mbm
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: parent.activated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
modules/nsbar/panels/NsSystemTray.qml
Normal file
100
modules/nsbar/panels/NsSystemTray.qml
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// NoSignal System Tray popout (off the tray pill). Grid of StatusNotifierItems;
|
||||||
|
// left-click activates, right-click opens the item's menu. Uses SystemTray.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import qs.services
|
||||||
|
import qs.components
|
||||||
|
|
||||||
|
NsPanel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 260
|
||||||
|
anchorMode: "right"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "System Tray"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.panelTitle
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 4
|
||||||
|
rowSpacing: 8
|
||||||
|
columnSpacing: 8
|
||||||
|
visible: SystemTray.items.values.length > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: SystemTray.items
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
id: cell
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
implicitWidth: 44
|
||||||
|
implicitHeight: 44
|
||||||
|
radius: Theme.radius.tile
|
||||||
|
color: cellMa.containsMouse ? Theme.hover : Theme.fillSubtle
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
source: cell.modelData?.icon ?? ""
|
||||||
|
sourceSize.width: 44
|
||||||
|
sourceSize.height: 44
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cellMa
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.RightButton)
|
||||||
|
cell.modelData?.display(cell, 0, height);
|
||||||
|
else if (cell.modelData?.onlyMenu)
|
||||||
|
cell.modelData?.display(cell, 0, height);
|
||||||
|
else
|
||||||
|
cell.modelData?.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.maximumWidth: 50
|
||||||
|
text: cell.modelData?.title || cell.modelData?.id || ""
|
||||||
|
color: Theme.textMuted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: SystemTray.items.values.length === 0
|
||||||
|
text: "No tray items"
|
||||||
|
color: Theme.textMuted
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pixelSize: Theme.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,15 @@ Item {
|
||||||
implicitWidth: layout.implicitWidth + Tokens.padding.large + layout.anchors.horizontalCenterOffset * 2
|
implicitWidth: layout.implicitWidth + Tokens.padding.large + layout.anchors.horizontalCenterOffset * 2
|
||||||
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
|
||||||
|
|
||||||
|
// NoSignal: flat square glass background matching the design popouts
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.panelBg
|
||||||
|
radius: 0
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.panelBorder
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: layout
|
id: layout
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue