Compare commits

..

16 commits

Author SHA1 Message Date
2 * r + 2 * t
a16c957a8b fix: use standard paths for default lyrics dir
Also use capitalised lyrics dir for consistency with wallpaper dir
2026-06-11 21:50:48 +10:00
2 * r + 2 * t
b80b9475ac fix: don't prefix lyrics backend strings with qualifier
Also store the lyrics map in the state dir instead of lyrics dir
2026-06-11 21:49:22 +10:00
2 * r + 2 * t
deb40a6536 fix: use overlay layer when on special ws and fs below 2026-06-11 00:23:55 +10:00
2 * r + 2 * t
92a4273cf5 chore: bump m3shapes again
To fix build warnings
2026-06-10 02:22:57 +10:00
2 * r + 2 * t
3c7df45505 fix: block inputs from going through menus 2026-06-09 23:05:55 +10:00
2 * r + 2 * t
362c1ec670 chore: bump m3shapes version 2026-06-09 21:44:22 +10:00
2 * r + 2 * t
c46593d438 fix: readme example conf workspace label 2026-06-09 19:21:04 +10:00
2 * r + 2 * t
3457164e19 fix: weather tab forecast when fahrenheit
Closes #1534
2026-06-09 19:18:56 +10:00
2 * r + 2 * t
a1124c8228
feat: expressive lock input (#1536)
* feat: shapes in lock input

* fix: better lock input placeholder

* feat: scale lock center properly + style improvements

* fix: use non anim placeholder width

* fix: not mono lock state text

* fix: use shape queue instead of random

* fix: use scale for enter button instead of state layer

* fix: no slant for lock state message

* fix: enter button pressable when no buffer
2026-06-09 19:07:18 +10:00
2 * r + 2 * t
63b522648e fix: expand tilde in lyrics path
Fixes #1532
2026-06-09 16:10:35 +10:00
2 * r + 2 * t
4684e8a24f feat: add workspace font config
No Rubik hardcoding
2026-06-09 16:04:03 +10:00
2 * r + 2 * t
ac13eec68c fix: button comps' styling 2026-06-09 15:39:12 +10:00
2 * r + 2 * t
0d37255873 fix: increase lock weather details threshold 2026-06-09 01:11:49 +10:00
2 * r + 2 * t
81d2a27f50 fix: reduce spacing for bar clock 2026-06-09 01:06:39 +10:00
2 * r + 2 * t
66a9f2f220 fix: clear colours preview when lock cleared
Closes #1522
2026-06-08 18:33:45 +10:00
2 * r + 2 * t
8a3bb46db6 chore: remove unused import
See this is why I should use PRs .w.
2026-06-08 17:36:03 +10:00
139 changed files with 964 additions and 4393 deletions

3
.gitattributes vendored
View file

@ -1,3 +0,0 @@
*.qml text
*.conf text
*.json text

View file

@ -75,10 +75,12 @@ endif()
if("m3shapes" IN_LIST ENABLE_MODULES)
message(STATUS "Fetching M3Shapes module")
include(FetchContent)
set(M3SHAPES_REV bdc327b29f95394a732baf3c9b19658ba23755b6)
FetchContent_Declare(
m3shapes_external
GIT_REPOSITORY https://github.com/soramanew/m3shapes.git
GIT_TAG 356825d31f16052a782735cce264331cba0cb71b
GIT_TAG ${M3SHAPES_REV}
SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/m3shapes-${M3SHAPES_REV}"
)
FetchContent_MakeAvailable(m3shapes_external)
message(STATUS "Done fetching M3Shapes module")

View file

@ -279,6 +279,7 @@ For example, to disable the bar on DP-1:
"font": {
"scale": 1,
"clock": "Rubik",
"workspaces": "Rubik",
"headline": {
"family": "GoogleSansFlex",
"large": { "size": 32, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
@ -433,7 +434,7 @@ For example, to disable the bar on DP-1:
"maxWindowIcons": 5,
"activeTrail": false,
"perMonitorWorkspaces": true,
"label": " ",
"label": " ",
"occupiedLabel": "󰮯",
"activeLabel": "󰮯",
"capitalisation": "preserve",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View file

@ -1,20 +0,0 @@
#%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

View file

@ -12,10 +12,10 @@ ColumnLayout {
spacing: Tokens.spacing.medium
Layout.alignment: Qt.AlignHCenter
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
animate: true
icon: root.icon
text: root.icon
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build()
}

View file

@ -1,32 +0,0 @@
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
}

View file

@ -1,43 +0,0 @@
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
}
}

View file

@ -1,46 +0,0 @@
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()
}
}

View file

@ -21,7 +21,7 @@ StyledRect {
property alias shapeMorph: stateLayer.shapeMorph
property bool fillWidth // For ButtonRow
property font font
property font font: Tokens.font.body.small
property int type: ButtonBase.Filled
property real padding

View file

@ -45,8 +45,8 @@ ColumnLayout {
Layout.fillWidth: true
}
NsIcon {
icon: "expand_more"
MaterialIcon {
text: "expand_more"
rotation: root.expanded ? 180 : 0
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium

View file

@ -110,11 +110,11 @@ RowLayout {
}
}
NsIcon {
MaterialIcon {
id: upIcon
anchors.centerIn: parent
icon: "keyboard_arrow_up"
text: "keyboard_arrow_up"
color: Colours.palette.m3onPrimary
}
}
@ -145,11 +145,11 @@ RowLayout {
onReleased: timer.stop()
}
NsIcon {
MaterialIcon {
id: downIcon
anchors.centerIn: parent
icon: "keyboard_arrow_down"
text: "keyboard_arrow_down"
color: Colours.palette.m3onPrimary
}
}

View file

@ -16,7 +16,7 @@ Slider {
background: StyledRect {
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: 0
radius: Tokens.rounding.full
StyledRect {
anchors.left: parent.left
@ -51,7 +51,7 @@ Slider {
anchors.fill: parent
color: Colours.palette.m3inverseSurface
radius: 0
radius: Tokens.rounding.full
MouseArea {
id: handleInteraction

View file

@ -6,7 +6,7 @@ import qs.services
ButtonBase {
id: root
property alias icon: label.icon
property alias icon: label.text
readonly property alias label: label
font: Tokens.font.icon.medium
@ -34,10 +34,11 @@ ButtonBase {
return h;
}
NsIcon {
MaterialIcon {
id: label
anchors.centerIn: parent
anchors.verticalCenterOffset: 1 // AHHHHHHH material symbols whyyyy
color: root.onColour
fontStyle: root.font
fill: !root.isToggle || root.internalChecked ? 1 : 0

View file

@ -7,7 +7,7 @@ import qs.services
ButtonBase {
id: root
property string icon
property alias icon: iconLabel.text
property alias text: label.text
readonly property alias iconLabel: iconLabel
@ -44,17 +44,15 @@ ButtonBase {
anchors.centerIn: parent
spacing: Tokens.spacing.small
NsIcon {
MaterialIcon {
id: iconLabel
icon: root.icon
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
color: root.onColour
fill: root.internalChecked ? 1 : 0
fontStyle: {
const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.1);
f.pointSize = Math.round(root.font.pointSize * 1.2);
return f;
}
@ -69,7 +67,7 @@ ButtonBase {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: -Math.round(iconLabel.fontInfo.pointSize * 0.0575)
Layout.topMargin: 1
color: root.onColour
font: root.font
}

View file

@ -94,6 +94,12 @@ MouseArea {
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onWheel: e => e.accepted = true
}
StyledRect {
anchors.fill: parent
radius: parent.radius
@ -157,9 +163,9 @@ MouseArea {
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
icon: item.modelData?.icon ?? ""
text: item.modelData?.icon ?? ""
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
}
@ -176,8 +182,8 @@ MouseArea {
active: item.modelData?.trailingIcon.length > 0
visible: active
sourceComponent: NsIcon {
icon: item.modelData.trailingIcon
sourceComponent: MaterialIcon {
text: item.modelData.trailingIcon
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
}
}

View file

@ -64,12 +64,12 @@ Row {
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
spacing: Tokens.spacing.small
NsIcon {
MaterialIcon {
id: iconLabel
Layout.alignment: Qt.AlignVCenter
animate: true
icon: root.active?.activeIcon ?? root.fallbackIcon
text: root.active?.activeIcon ?? root.fallbackIcon
color: root.disabled ? root.disabledTextColour : root.textColour
fill: 1
}
@ -116,13 +116,13 @@ Row {
onClicked: root.expanded = !root.expanded
}
NsIcon {
MaterialIcon {
id: expandIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
icon: "expand_more"
text: "expand_more"
color: root.disabled ? root.disabledTextColour : root.textColour
rotation: root.expanded ? 180 : 0

View file

@ -39,5 +39,6 @@ ButtonBase {
anchors.centerIn: parent
color: root.onColour
font: root.font
}
}

View file

@ -54,12 +54,12 @@ StyledRect {
anchors.centerIn: parent
spacing: Tokens.spacing.medium
NsIcon {
MaterialIcon {
id: toggleBtnIcon
visible: !!text
fill: root.toggled ? 1 : 0
icon: root.icon
text: root.icon
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
fontStyle: Tokens.font.icon.size(root.iconSize).build()

View file

@ -57,11 +57,11 @@ Item {
}
}
NsIcon {
MaterialIcon {
anchors.centerIn: parent
grade: 200
icon: image.status === Image.Error ? "broken_image" : "art_track"
text: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0

7
flake.lock generated
View file

@ -24,16 +24,17 @@
"m3shapes": {
"flake": false,
"locked": {
"lastModified": 1777812556,
"narHash": "sha256-E5nqOucRQBWKuT31AMekmfgMywRdHgSbU86R7t4BKKA=",
"lastModified": 1781017666,
"narHash": "sha256-kfHyzZaPHgqZML48OA+5JwBOsLdQJ2ci/aGPShvUB4Y=",
"owner": "soramanew",
"repo": "m3shapes",
"rev": "356825d31f16052a782735cce264331cba0cb71b",
"rev": "bdc327b29f95394a732baf3c9b19658ba23755b6",
"type": "github"
},
"original": {
"owner": "soramanew",
"repo": "m3shapes",
"rev": "bdc327b29f95394a732baf3c9b19658ba23755b6",
"type": "github"
}
},

View file

@ -16,7 +16,7 @@
};
m3shapes = {
url = "github:soramanew/m3shapes/356825d31f16052a782735cce264331cba0cb71b";
url = "github:soramanew/m3shapes/bdc327b29f95394a732baf3c9b19658ba23755b6";
flake = false;
};
};

View file

@ -54,9 +54,8 @@ Scope {
onPressed: {
if (root.hasFullscreen)
return;
// NoSignal: drive the redesigned full-screen Power Menu (NsOverlay),
// not the old caelestia session panel (now dead code).
NsShell.toggle("power", 0);
const visibilities = Visibilities.getForActive();
visibilities.session = !visibilities.session;
}
}

View file

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

View file

@ -17,40 +17,35 @@ Item {
readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name)
// NoSignal horizontal top bar: the bar's "thickness" is its HEIGHT (was width
// for the upstream vertical left-edge bar). clampedHeight/contentHeight and
// implicitHeight carry the thickness; the wrapper fills the screen width.
readonly property int clampedHeight: Math.max(Config.border.minThickness, implicitHeight)
readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth)
readonly property int padding: Math.max(Tokens.padding.small, Config.border.thickness)
readonly property int contentHeight: Tokens.sizes.bar.innerWidth + padding * 2
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentHeight : Config.border.thickness
// 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
readonly property int contentWidth: Tokens.sizes.bar.innerWidth + padding * 2
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness
readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
property bool isHovered
function closeTray(): void {
(content.item as Bar)?.closeTray();
}
function checkPopout(x: real): void {
(content.item as Bar)?.checkPopout(x);
function checkPopout(y: real): void {
(content.item as Bar)?.checkPopout(y);
}
function handleWheel(x: real, angleDelta: point): void {
(content.item as Bar)?.handleWheel(x, angleDelta);
function handleWheel(y: real, angleDelta: point): void {
(content.item as Bar)?.handleWheel(y, angleDelta);
}
clip: true
visible: height > Config.border.thickness
implicitHeight: fullscreen ? 0 : Config.border.thickness
visible: width > Config.border.thickness
implicitWidth: fullscreen ? 0 : Config.border.thickness
states: State {
name: "visible"
when: root.shouldBeVisible
PropertyChanges {
root.implicitHeight: root.contentHeight
root.implicitWidth: root.contentWidth
}
}
@ -61,7 +56,7 @@ Item {
Anim {
target: root
property: "implicitHeight"
property: "implicitWidth"
}
},
Transition {
@ -70,7 +65,7 @@ Item {
Anim {
target: root
property: "implicitHeight"
property: "implicitWidth"
type: Anim.Emphasized
}
}
@ -79,14 +74,14 @@ Item {
Loader {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
active: root.shouldBeVisible
sourceComponent: Bar {
height: root.contentHeight
width: root.contentWidth
screen: root.screen
visibilities: root.visibilities
popouts: root.popouts // qmllint disable incompatible-type

View file

@ -1,34 +1,106 @@
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.Layouts
import Caelestia.Config
import qs.components
import qs.services
RowLayout {
StyledRect {
id: root
spacing: 8
readonly property color colour: Colours.palette.m3tertiary
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)
StyledText {
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
}
implicitWidth: Tokens.sizes.bar.innerWidth
implicitHeight: layout.implicitHeight + root.padding * 2
StyledText {
Layout.alignment: Qt.AlignVCenter
text: Time.format("hh:mm")
color: Theme.text
font.family: Theme.font.family
font.pixelSize: Theme.font.bar
font.weight: Font.DemiBold
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.full
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall
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
}
}
}
}

View file

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

View file

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

View file

@ -24,7 +24,7 @@ ColumnLayout {
readonly property bool isOccupied: occupied[ws] ?? false
readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
Layout.alignment: Qt.AlignVCenter
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: size
spacing: 0
@ -52,7 +52,7 @@ ColumnLayout {
}
color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2)
verticalAlignment: Qt.AlignVCenter
font.family: "Rubik" // Hard code rubik for now since google sans doesn't play well with certain unicode symbols apparently
font.family: Tokens.font.workspaces
}
Loader {

View file

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

View file

@ -12,24 +12,21 @@ Item {
required property real borderThickness
readonly property alias content: content
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
property real offsetScale: x > 0 || content.hasCurrent ? 0 : 1
visible: width > 0 && height > 0
clip: true
// NoSignal top bar: popouts drop DOWN from the bar. The collapsing axis is
// 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)
implicitWidth: content.implicitWidth * (1 - offsetScale)
implicitHeight: content.implicitHeight
y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0
x: {
x: content.isDetached ? (parent.width - content.nonAnimWidth) / 2 : 0
y: {
if (content.isDetached)
return (parent.width - content.nonAnimWidth) / 2;
return (parent.height - content.nonAnimHeight) / 2;
const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
const off = content.currentCenter - borderThickness - content.nonAnimHeight / 2;
const diff = parent.height - Math.floor(off + content.nonAnimHeight);
if (diff < 0)
return off + diff;
return Math.max(off, 0);
@ -39,14 +36,14 @@ Item {
Anim {}
}
Behavior on y {
Behavior on x {
Anim {
duration: content.animLength
easing: content.animCurve
}
}
Behavior on x {
Behavior on y {
enabled: root.offsetScale < 1
Anim {
@ -61,8 +58,8 @@ Item {
screen: root.screen
offsetScale: root.offsetScale
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * root.offsetScale
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: (-implicitWidth - 5) * root.offsetScale
}
}

View file

@ -149,14 +149,14 @@ Item {
}
}
Behavior on implicitHeight {
Behavior on implicitWidth {
Anim {
duration: root.animLength
easing: root.animCurve
}
}
Behavior on implicitWidth {
Behavior on implicitHeight {
enabled: root.offsetScale < 1
Anim {

View file

@ -75,7 +75,7 @@ Item {
anchors.bottom: parent.bottom
anchors.margins: Tokens.padding.large
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.large
color: "transparent"
Flickable {

View file

@ -21,7 +21,7 @@ GridLayout {
Layout.preferredWidth: Tokens.sizes.dashboard.userWidth
Layout.fillHeight: true
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge
User {
id: user
@ -37,7 +37,7 @@ GridLayout {
Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
Layout.preferredHeight: weather.implicitHeight
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge * 1.5
SmallWeather {
id: weather
@ -49,7 +49,7 @@ GridLayout {
Layout.preferredWidth: dateTime.implicitWidth
Layout.fillHeight: true
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.large
DateTime {
id: dateTime
@ -63,7 +63,7 @@ GridLayout {
Layout.fillWidth: true
Layout.preferredHeight: calendar.implicitHeight
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge
Calendar {
id: calendar
@ -78,7 +78,7 @@ GridLayout {
Layout.preferredWidth: resources.implicitWidth
Layout.fillHeight: true
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.large
Resources {
id: resources
@ -92,7 +92,7 @@ GridLayout {
Layout.preferredWidth: media.implicitWidth
Layout.fillHeight: true
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge * 2
Media {
id: media

View file

@ -102,11 +102,11 @@ Item {
CAnim {}
}
NsIcon {
MaterialIcon {
id: icon
anchors.centerIn: parent
icon: "queue_music"
text: "queue_music"
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
color: Colours.palette.m3onPrimaryContainer
}

View file

@ -23,9 +23,9 @@ Item {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.medium
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "tune"
text: "tune"
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
color: Colours.palette.m3onSurfaceVariant
}

View file

@ -86,7 +86,7 @@ Item {
implicitHeight: parent.implicitHeight * 2
color: Colours.palette.m3primary
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.full
}
Behavior on x {
@ -148,18 +148,18 @@ Item {
anchors.verticalCenter: parent.verticalCenter
implicitHeight: parent.height + Tokens.sizes.dashboard.tabIndicatorSpacing * 2
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.medium
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
onClicked: root.dashState.currentTab = tab.TabBar.index
}
NsIcon {
MaterialIcon {
id: icon
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: label.top
icon: tab.iconName
text: tab.iconName
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
fill: tab.current ? 1 : 0
fontStyle: Tokens.font.icon.medium

View file

@ -67,7 +67,7 @@ Item {
Layout.fillWidth: true
implicitHeight: bigInfoRow.implicitHeight + Tokens.padding.small
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge * 2
color: Colours.tPalette.m3surfaceContainer
RowLayout {
@ -76,9 +76,9 @@ Item {
anchors.centerIn: parent
spacing: Tokens.spacing.largeIncreased
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
icon: Weather.icon
text: Weather.icon
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build()
color: Colours.palette.m3secondary
animate: true
@ -155,7 +155,7 @@ Item {
Layout.fillWidth: true
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
@ -180,16 +180,20 @@ Item {
color: Colours.palette.m3onSurfaceVariant
}
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: forecastItem.modelData.icon
text: forecastItem.modelData.icon
fontStyle: Tokens.font.icon.extraLarge
color: Colours.palette.m3secondary
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: GlobalConfig.services.useFahrenheit ? forecastItem.modelData.maxTempF + "°" + " / " + forecastItem.modelData.minTempF + "°" : forecastItem.modelData.maxTempC + "°" + " / " + forecastItem.modelData.minTempC + "°"
text: {
const min = Weather.formatTemp(forecastItem.modelData.minTempC).slice(0, -1);
const max = Weather.formatTemp(forecastItem.modelData.maxTempC).slice(0, -1);
return `${min} / ${max}`;
}
font: Tokens.font.body.builders.small.weight(Font.DemiBold).build()
color: Colours.palette.m3tertiary
}
@ -209,15 +213,15 @@ Item {
Layout.fillWidth: true
Layout.preferredHeight: 60
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.medium
color: Colours.tPalette.m3surfaceContainer
Row {
anchors.centerIn: parent
spacing: Tokens.spacing.medium
NsIcon {
icon: detailRoot.icon
MaterialIcon {
text: detailRoot.icon
color: detailRoot.colour
fontStyle: Tokens.font.icon.large
anchors.verticalCenter: parent.verticalCenter
@ -252,8 +256,8 @@ Item {
spacing: Tokens.spacing.small
NsIcon {
icon: weatherStat.icon
MaterialIcon {
text: weatherStat.icon
fontStyle: Tokens.font.icon.extraLarge
color: weatherStat.colour
}

View file

@ -62,7 +62,7 @@ CustomMouseArea {
StateLayer {
color: Colours.palette.m3primary
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: pressed ? Tokens.rounding.small : Tokens.rounding.large
disabled: {
const now = new Date();
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();

View file

@ -65,10 +65,10 @@ Item {
Anim {}
}
NsIcon {
MaterialIcon {
anchors.centerIn: parent
icon: res.icon
fontStyle: Tokens.font.icon.large
text: res.icon
font: Tokens.font.icon.large
color: res.fgColour
}
}

View file

@ -13,14 +13,14 @@ Item {
Component.onCompleted: Weather.reload()
NsIcon {
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
animate: true
icon: Weather.icon
text: Weather.icon
color: Colours.palette.m3secondary
fontStyle: Tokens.font.icon.builders.extraLarge.scale(1.6).build()
}

View file

@ -75,8 +75,8 @@ Item {
asynchronous: true
active: pfp.status !== Image.Ready
sourceComponent: NsIcon {
icon: "person_add"
sourceComponent: MaterialIcon {
text: "person_add"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge
fill: 1
@ -120,9 +120,9 @@ Item {
}
}
NsIcon {
MaterialIcon {
anchors.centerIn: parent
icon: "person_edit"
text: "person_edit"
color: Colours.palette.m3onPrimary
fontStyle: Tokens.font.icon.large
}
@ -187,9 +187,9 @@ Item {
CAnim {}
}
NsIcon {
MaterialIcon {
anchors.centerIn: parent
icon: "clock_arrow_up"
text: "clock_arrow_up"
color: Colours.palette.m3onTertiaryContainer
fontStyle: Tokens.font.icon.medium
}
@ -216,7 +216,7 @@ Item {
implicitWidth: 10
implicitHeight: 10
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.full
color: Colours.palette.m3secondaryContainer
}
@ -229,7 +229,7 @@ Item {
implicitWidth: 15
implicitHeight: 15
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.full
color: Colours.palette.m3secondaryContainer
}
@ -240,7 +240,7 @@ Item {
anchors.leftMargin: -Tokens.padding.medium
y: Tokens.padding.extraSmall
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.largeIncreased
color: Colours.palette.m3secondaryContainer
implicitWidth: wmLabel.implicitWidth + Tokens.padding.medium * 2
implicitHeight: wmLabel.implicitHeight + Tokens.padding.small * 2
@ -251,11 +251,11 @@ Item {
anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall
NsIcon {
MaterialIcon {
id: wmIcon
anchors.verticalCenter: parent.verticalCenter
icon: "select_window"
text: "select_window"
color: Colours.palette.m3onSecondaryContainer
fontStyle: wmText.font
}

View file

@ -176,7 +176,7 @@ Item {
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
color: Colours.palette.m3primaryContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.full
LoadingIndicator {
id: shape
@ -212,9 +212,9 @@ Item {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "sentiment_sad"
text: "sentiment_sad"
fontStyle: Tokens.font.icon.builders.large.scale(2).build()
color: Colours.palette.m3outline
}

View file

@ -20,9 +20,9 @@ Item {
spacing: Tokens.spacing.medium
z: 1
NsIcon {
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
icon: "lyrics"
text: "lyrics"
fontStyle: Tokens.font.icon.medium
}

View file

@ -33,7 +33,7 @@ Item {
anchors.fill: parent
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
group: blobGroup
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.medium
Behavior on anchors.margins {
Anim {}
@ -50,7 +50,7 @@ Item {
implicitHeight: parent.height
group: blobGroup
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.medium
deformScale: 0.00001
states: State {
@ -197,11 +197,11 @@ Item {
hoverEnabled: true
onClicked: root.open = !root.open
NsIcon {
MaterialIcon {
id: icon
anchors.centerIn: parent
icon: "more_vert"
text: "more_vert"
fontStyle: Tokens.font.icon.medium
}
}

View file

@ -12,7 +12,7 @@ StyledClippingRect {
property real animPerc: UPower.displayDevice.percentage
color: Colours.palette.m3secondaryContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.large
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
@ -39,7 +39,7 @@ StyledClippingRect {
implicitHeight: parent.height * root.animPerc
color: Colours.palette.m3secondary
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraSmall
clip: true
Contents {
@ -65,9 +65,9 @@ StyledClippingRect {
spacing: 0
NsIcon {
MaterialIcon {
Layout.leftMargin: -Tokens.padding.extraSmall
icon: "battery_full"
text: "battery_full"
color: contents.accentColour
fontStyle: Tokens.font.icon.large
}
@ -115,8 +115,8 @@ StyledClippingRect {
Layout.alignment: Qt.AlignRight
spacing: Tokens.spacing.extraSmall
NsIcon {
icon: "bolt"
MaterialIcon {
text: "bolt"
color: contents.accentColour
fontStyle: Tokens.font.icon.large
fill: 1

View file

@ -17,7 +17,7 @@ StyledRect {
required property real temperature
color: Colours.tPalette.m3surfaceContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge
implicitWidth: Tokens.sizes.dashboard.perfHeroCardWidth
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Tokens.spacing.large, usageShape.implicitHeight + usageLabel.implicitHeight) + Tokens.padding.large * 2
@ -40,11 +40,11 @@ StyledRect {
Anim {}
}
NsIcon {
MaterialIcon {
id: icon
anchors.centerIn: parent
icon: root.icon
text: root.icon
color: root.accent
fontStyle: Tokens.font.icon.medium
}
@ -84,9 +84,9 @@ StyledRect {
Layout.leftMargin: -Tokens.padding.extraSmall
spacing: Tokens.spacing.extraSmall
NsIcon {
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
icon: root.temperature > 90 ? "thermometer_alert" : "thermometer"
text: root.temperature > 90 ? "thermometer_alert" : "thermometer"
color: root.temperature > 90 ? Colours.palette.m3error : root.accent
fontStyle: Tokens.font.icon.medium
fill: 1

View file

@ -12,7 +12,7 @@ StyledRect {
readonly property color accent: Colours.palette.m3tertiary
color: Colours.tPalette.m3surfaceContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.medium
implicitWidth: layout.implicitWidth + Tokens.padding.extraLargeIncreased * 2
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
@ -31,8 +31,8 @@ StyledRect {
Layout.leftMargin: -Tokens.padding.extraSmall
spacing: Tokens.spacing.small
NsIcon {
icon: "memory_alt"
MaterialIcon {
text: "memory_alt"
fill: 1
color: root.accent
fontStyle: Tokens.font.icon.builders.medium.weight(Font.DemiBold).build() // DemiBold to fix fill issues

View file

@ -10,7 +10,7 @@ StyledRect {
id: root
color: Colours.tPalette.m3surfaceContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraLarge
implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth
implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight
@ -30,8 +30,8 @@ StyledRect {
RowLayout {
spacing: Tokens.spacing.small
NsIcon {
icon: "swap_vert"
MaterialIcon {
text: "swap_vert"
color: Colours.palette.m3primary
fontStyle: Tokens.font.icon.medium
}
@ -105,8 +105,8 @@ StyledRect {
Layout.fillWidth: true
spacing: Tokens.spacing.small
NsIcon {
icon: "download"
MaterialIcon {
text: "download"
color: Colours.palette.m3tertiary
fontStyle: Tokens.font.icon.medium
}
@ -136,8 +136,8 @@ StyledRect {
Layout.fillWidth: true
spacing: Tokens.spacing.small
NsIcon {
icon: "upload"
MaterialIcon {
text: "upload"
color: Colours.palette.m3secondary
fontStyle: Tokens.font.icon.medium
}
@ -167,8 +167,8 @@ StyledRect {
Layout.fillWidth: true
spacing: Tokens.spacing.small
NsIcon {
icon: "history"
MaterialIcon {
text: "history"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
}

View file

@ -14,7 +14,7 @@ StyledRect {
readonly property real percentage: Storage.primaryDisk?.perc ?? 0
color: Colours.tPalette.m3surfaceContainer
radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
radius: Tokens.rounding.extraExtraLarge
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
@ -55,9 +55,9 @@ StyledRect {
anchors.centerIn: parent
spacing: 0
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "hard_drive"
text: "hard_drive"
color: root.accent
fontStyle: Tokens.font.icon.medium
}

View file

@ -21,6 +21,7 @@ StyledWindow {
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0
readonly property bool hasFullscreenOnNormalWs: monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false
readonly property bool hasFullscreen: {
if (hasSpecialWorkspace) {
const specialName = monitor?.lastIpcObject.specialWorkspace?.name;
@ -29,13 +30,13 @@ StyledWindow {
const specialWs = Hypr.workspaces.values.find(ws => ws.name === specialName);
return specialWs?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
}
return monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
return hasFullscreenOnNormalWs;
}
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 borderThickness: 0 // NoSignal: no screen-edge frame (NsBar owns the top)
readonly property real borderRounding: 0 // NoSignal: no rounded screen frame
readonly property real borderThickness: contentItem.Config.border.thickness * (1 - fsTransitionProg)
readonly property real borderRounding: contentItem.Config.border.rounding * (1 - fsTransitionProg)
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
readonly property real borderLayoutThickness: hasFullscreen ? 0 : contentItem.Config.border.thickness
@ -64,7 +65,7 @@ StyledWindow {
name: "drawers"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: fsTransitionProg > 0 && contentItem.Config.general.showOverFullscreen ? WlrLayer.Overlay : WlrLayer.Top
WlrLayershell.layer: (fsTransitionProg > 0 && contentItem.Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.session ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
mask: hasFullscreen ? emptyRegion : regions
@ -85,14 +86,14 @@ StyledWindow {
Region {
id: emptyRegion
x: panels.notifications.x + root.borderThickness
y: panels.notifications.y + bar.implicitHeight
x: panels.notifications.x + bar.implicitWidth
y: panels.notifications.y + root.borderThickness
width: panels.notifications.width
height: panels.notifications.height
Region {
x: root.width - width
y: panels.osdWrapper.y + bar.implicitHeight
y: panels.osdWrapper.y + root.borderThickness
width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness
height: panels.osd.height
}
@ -155,9 +156,9 @@ StyledWindow {
anchors.margins: -50 // Make border thicker to smooth out bulge from closed drawers
group: blobGroup
radius: root.borderRounding
borderLeft: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderLeft: bar.implicitWidth - anchors.margins - root.sdfBorderOffset
borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderTop: -anchors.margins - root.sdfBorderOffset // NoSignal: no top frame
borderTop: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset
}
@ -180,7 +181,7 @@ StyledWindow {
panel: panels.sessionWrapper
deformAmount: 0.2
x: panels.sessionWrapper.x + panels.session.x + root.borderThickness
x: panels.sessionWrapper.x + panels.session.x + bar.implicitWidth
implicitWidth: panels.session.width
}
@ -198,10 +199,8 @@ StyledWindow {
id: osdBg
panel: panels.osdWrapper
deformAmount: 0 // NoSignal: square OSD (no organic blob deformation)
radius: 0
opacity: 0 // NoSignal: OSD draws its own flat square glass bg (osd/Content)
x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
deformAmount: 0.25
x: panels.osdWrapper.x + panels.osd.x + bar.implicitWidth
implicitWidth: panels.osd.width
}
@ -209,12 +208,6 @@ StyledWindow {
id: notifsBg
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 {
@ -234,7 +227,7 @@ StyledWindow {
panel: panels.popoutsWrapper
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
x: panels.popoutsWrapper.x + panels.popouts.x + root.borderThickness - panels.popouts.width * extraWidth
x: panels.popoutsWrapper.x + panels.popouts.x + bar.implicitWidth - panels.popouts.width * extraWidth
implicitWidth: panels.popouts.width * (1 + extraWidth)
Behavior on extraWidth {
@ -300,9 +293,8 @@ StyledWindow {
BarWrapper {
id: bar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
screen: root.screen
visibilities: visibilities
@ -319,8 +311,8 @@ StyledWindow {
property real deformAmount: 0.15
group: blobGroup
x: panel.x + root.borderThickness
y: panel.y + bar.implicitHeight
x: panel.x + bar.implicitWidth
y: panel.y + root.borderThickness
implicitWidth: panel.width
implicitHeight: panel.height
radius: Tokens.rounding.extraLarge

View file

@ -14,11 +14,11 @@ Scope {
ExclusionZone {
anchors.left: true
exclusiveZone: root.bar.exclusiveZone
}
ExclusionZone {
anchors.top: true
exclusiveZone: 0 // NoSignal: NsBar owns the top exclusive zone
}
ExclusionZone {
@ -32,7 +32,7 @@ Scope {
component ExclusionZone: StyledWindow {
screen: root.screen
name: "border-exclusion"
exclusiveZone: 0 // NoSignal: no screen-edge frame
exclusiveZone: contentItem.Config.border.thickness
mask: Region {}
implicitWidth: 1
implicitHeight: 1

View file

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

View file

@ -36,7 +36,7 @@ Item {
anchors.fill: parent
anchors.margins: borderThickness
anchors.topMargin: bar.implicitHeight
anchors.leftMargin: bar.implicitWidth
Item {
id: osdWrapper
@ -70,7 +70,7 @@ Item {
sessionPanel: sessionWrapper
utilitiesPanel: utilities
y: 46 // NoSignal: drop the toasts below the 38px NsBar (+ gap)
anchors.top: parent.top
anchors.right: parent.right
}

View file

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

View file

@ -58,14 +58,14 @@ Item {
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
NsIcon {
MaterialIcon {
id: searchIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.padding
icon: "search"
text: "search"
color: Colours.palette.m3onSurfaceVariant
}
@ -145,7 +145,7 @@ Item {
}
}
NsIcon {
MaterialIcon {
id: clearIcon
anchors.verticalCenter: parent.verticalCenter
@ -163,7 +163,7 @@ Item {
return 1;
}
icon: "close"
text: "close"
color: Colours.palette.m3onSurfaceVariant
MouseArea {

View file

@ -116,8 +116,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
NsIcon {
icon: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
MaterialIcon {
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge

View file

@ -25,11 +25,11 @@ Item {
anchors.rightMargin: Tokens.padding.medium
anchors.margins: Tokens.padding.small
NsIcon {
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
icon: root.modelData?.icon ?? ""
text: root.modelData?.icon ?? ""
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.large.scale(1.3).build()
}

View file

@ -79,8 +79,8 @@ Item {
anchors.right: parent.right
active: root.modelData && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, root.modelData.id)
sourceComponent: NsIcon {
icon: "favorite"
sourceComponent: MaterialIcon {
text: "favorite"
fill: 1
color: Colours.palette.m3primary
}

View file

@ -40,8 +40,8 @@ Item {
spacing: Tokens.spacing.medium
NsIcon {
icon: "function"
MaterialIcon {
text: "function"
fontStyle: Tokens.font.icon.extraLarge
Layout.alignment: Qt.AlignVCenter
}
@ -105,14 +105,14 @@ Item {
}
}
NsIcon {
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Tokens.padding.medium
icon: "open_in_new"
text: "open_in_new"
color: Colours.palette.m3onTertiary
fontStyle: Tokens.font.icon.large
}

View file

@ -92,8 +92,8 @@ Item {
active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme
sourceComponent: NsIcon {
icon: "check"
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}

View file

@ -26,10 +26,10 @@ Item {
anchors.rightMargin: Tokens.padding.medium
anchors.margins: Tokens.padding.small
NsIcon {
MaterialIcon {
id: icon
icon: root.modelData?.icon ?? ""
text: root.modelData?.icon ?? ""
fontStyle: Tokens.font.icon.extraLarge
anchors.verticalCenter: parent.verticalCenter
@ -68,8 +68,8 @@ Item {
active: root.modelData?.variant === Schemes.currentVariant
sourceComponent: NsIcon {
icon: "check"
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}

View file

@ -57,9 +57,9 @@ Item {
implicitWidth: Tokens.sizes.launcher.wallpaperWidth
implicitHeight: implicitWidth / 16 * 9
NsIcon {
MaterialIcon {
anchors.centerIn: parent
icon: "image"
text: "image"
color: Colours.tPalette.m3outline
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build()
}

View file

@ -41,6 +41,7 @@ ColumnLayout {
PasswordInput {
Layout.alignment: Qt.AlignHCenter
centerScale: Math.max(0.8, root.centerScale)
centerWidth: root.centerWidth
lock: root.lock
}

View file

@ -1,22 +1,11 @@
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.Layouts
import QtQuick.Effects
import Quickshell.Wayland
import Quickshell.Bluetooth
import Quickshell.Services.UPower
import Caelestia.Config
import qs.components
import qs.services
import qs.utils
WlSessionLockSurface {
id: root
@ -24,26 +13,153 @@ WlSessionLockSurface {
required property WlSessionLock lock
required property Pam pam
readonly property bool errored: root.pam.state === "error" || root.pam.state === "fail"
readonly property alias unlocking: unlockAnim.running
contentItem.Config.screen: screen?.name ?? ""
contentItem.Tokens.screen: screen?.name ?? ""
contentItem.Config.screen: screen.name
contentItem.Tokens.screen: screen.name
color: "transparent"
Connections {
function onUnlock(): void {
root.lock.locked = false;
unlockAnim.start();
}
target: root.lock
}
// Blurred wallpaper + scrim
SequentialAnimation {
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 {
id: background
anchors.fill: parent
captureSource: root.screen
opacity: 0
layer.enabled: true
layer.effect: MultiEffect {
@ -52,265 +168,57 @@ WlSessionLockSurface {
blur: 1
blurMax: 64
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 {
anchors.fill: parent
focus: true
Component.onCompleted: forceActiveFocus()
Keys.onPressed: event => root.pam.handleKey(event)
}
id: lockContent
// Top status row
RowLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 22
anchors.leftMargin: 28
anchors.rightMargin: 28
readonly property int size: lockIcon.implicitHeight + Tokens.padding.large * 4
readonly property int radius: size / 4 * Tokens.rounding.scale
RowLayout {
spacing: 7
NsIcon {
icon: "lock"
color: Theme.textMuted
size: 15
}
StyledText {
text: "Locked"
color: Theme.textMuted
font.family: Theme.font.family
font.pixelSize: Theme.font.bar
}
}
Item {
Layout.fillWidth: true
}
RowLayout {
spacing: 14
NsIcon {
icon: Nmcli.active ? Icons.getNetworkIcon(Nmcli.active.strength ?? 0) : "wifi_off"
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
}
}
}
}
// Centre block
ColumnLayout {
anchors.centerIn: parent
spacing: 0
implicitWidth: size
implicitHeight: size
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
}
rotation: 180
scale: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Time.format("ddd, d MMMM yyyy")
color: Theme.textMuted
font.family: Theme.font.family
font.pixelSize: 15
}
StyledRect {
id: lockBg
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
anchors.fill: parent
color: Colours.palette.m3surface
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)
}
}
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 14
text: SysInfo.user || "user"
color: Theme.text
font.family: Theme.font.family
font.pixelSize: 14
MaterialIcon {
id: lockIcon
anchors.centerIn: parent
text: "lock"
fontStyle: Tokens.font.icon.builders.extraLarge.scale(4).weight(Font.Bold).build()
rotation: 180
}
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)
Content {
id: content
RowLayout {
anchors.fill: parent
anchors.leftMargin: 14
anchors.rightMargin: 12
spacing: 10
anchors.centerIn: parent
width: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult * Tokens.sizes.lock.ratio - Tokens.padding.extraLargeIncreased
height: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult - Tokens.padding.extraLargeIncreased
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
lock: root
opacity: 0
scale: 0
}
}
}

View file

@ -40,12 +40,7 @@ Scope {
PamContext {
id: 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"
config: "passwd"
configDirectory: Quickshell.shellDir + "/assets/pam.d"
onMessageChanged: {

View file

@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import M3Shapes
import Caelestia.Config
import qs.components
import qs.services
@ -10,9 +11,19 @@ import qs.modules.lock
Item {
id: root
required property real centerScale
required property Pam pam
readonly property alias placeholder: placeholder
readonly property alias placeholderWidth: nonAnimPlaceholder.width
property string buffer
readonly property list<int> shapeQueue: {
const shapes = [MaterialShape.Slanted, MaterialShape.Arch, MaterialShape.Fan, MaterialShape.Arrow, MaterialShape.SemiCircle, MaterialShape.Triangle, MaterialShape.Diamond, MaterialShape.ClamShell, MaterialShape.Pentagon, MaterialShape.Gem, MaterialShape.Sunny, MaterialShape.VerySunny, MaterialShape.Cookie4Sided, MaterialShape.Ghostish, MaterialShape.SoftBurst];
for (let i = shapes.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shapes[i], shapes[j]] = [shapes[j], shapes[i]];
}
return shapes;
}
clip: true
@ -31,22 +42,30 @@ Item {
target: root.pam
}
StyledText {
id: placeholder
anchors.centerIn: parent
TextMetrics {
id: nonAnimPlaceholder
text: {
if (root.pam.passwd.active)
return qsTr("Loading...");
if (root.pam.state === "max")
return qsTr("You have reached the maximum number of tries");
return qsTr("Max tries reached");
return qsTr("Enter your password");
}
font: placeholder.font
}
StyledText {
id: placeholder
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
text: nonAnimPlaceholder.text
animate: true
color: root.pam.passwd.active ? Colours.palette.m3secondary : Colours.palette.m3outline
font: Tokens.font.mono.medium
font: Tokens.font.body.builders.medium.scale(root.centerScale).width(110).build()
opacity: root.buffer ? 0 : 1
@ -60,7 +79,12 @@ Item {
ListView {
id: charList
readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
readonly property int fullWidth: {
let w = (count - 1) * spacing;
for (let i = 0; i < count; i++)
w += ((itemAtIndex(i) as CharItem)?.nonAnimWidthScale ?? 1) * implicitHeight;
return w + implicitHeight; // Extra padding at ends
}
function bindImWidth(): void {
imWidthBehavior.enabled = false;
@ -82,63 +106,7 @@ Item {
values: root.buffer.split("")
}
delegate: StyledRect {
id: ch
implicitWidth: implicitHeight
implicitHeight: charList.implicitHeight
color: Colours.palette.m3onSurface
radius: Tokens.rounding.medium / 2
opacity: 0
scale: 0
Component.onCompleted: {
opacity = 1;
scale = 1;
}
ListView.onRemove: removeAnim.start()
SequentialAnimation {
id: removeAnim
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: true
}
ParallelAnimation {
Anim {
type: Anim.DefaultEffects
target: ch
property: "opacity"
to: 0
}
Anim {
target: ch
property: "scale"
to: 0.5
}
}
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: false
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
delegate: CharItem {}
Behavior on implicitWidth {
id: imWidthBehavior
@ -146,4 +114,121 @@ Item {
Anim {}
}
}
component CharItem: Item {
id: char
required property int index
property real nonAnimWidthScale: 1
implicitHeight: charList.implicitHeight
ListView.onRemove: {
initAnim.stop();
removeAnim.start();
}
MaterialShape {
id: charShape
anchors.centerIn: parent
implicitSize: charList.implicitHeight * 1.5
shape: root.shapeQueue[char.index % root.shapeQueue.length] ?? MaterialShape.Circle
color: Colours.palette.m3onSurface
Behavior on color {
CAnim {}
}
SequentialAnimation {
id: initAnim
running: true
ParallelAnimation {
Anim {
target: charShape
property: "opacity"
from: 0
to: 1
type: Anim.DefaultEffects
}
Anim {
target: charShape
property: "scale"
from: 0
to: 1
type: Anim.FastSpatial
}
Anim {
target: char
property: "implicitWidth"
from: charList.implicitHeight
to: charList.implicitHeight * 1.3
type: Anim.DefaultEffects
}
PropertyAction {
target: char
property: "nonAnimWidthScale"
value: 1.5
}
}
PauseAnimation {
duration: 180 * Tokens.anim.durations.scale
}
PropertyAction {
target: charShape
property: "shape"
value: MaterialShape.Circle
}
ParallelAnimation {
Anim {
target: charShape
property: "scale"
to: 2 / 3
type: Anim.FastSpatial
}
Anim {
target: char
property: "implicitWidth"
to: charList.implicitHeight
type: Anim.DefaultEffects
}
PropertyAction {
target: char
property: "nonAnimWidthScale"
value: 1
}
}
}
SequentialAnimation {
id: removeAnim
PropertyAction {
target: char
property: "ListView.delayRemove"
value: true
}
ParallelAnimation {
Anim {
type: Anim.DefaultEffects
target: charShape
property: "opacity"
to: 0
}
Anim {
target: charShape
property: "scale"
to: 0.5
}
}
PropertyAction {
target: char
property: "ListView.delayRemove"
value: false
}
}
}
}
}

View file

@ -1,5 +1,8 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import M3Shapes
import Caelestia.Config
import qs.components
import qs.components.controls
@ -8,10 +11,14 @@ import qs.services
StyledRect {
id: root
required property real centerScale
required property int centerWidth
required property var lock
implicitWidth: centerWidth * 0.8
implicitWidth: {
const w = centerWidth * 0.8;
return lock.pam.buffer ? w : Math.min(w, inputField.placeholderWidth + iconWrapper.implicitWidth + enterButton.implicitWidth + input.spacing * 2 + Tokens.padding.medium * 2);
}
implicitHeight: input.implicitHeight + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer
@ -33,6 +40,10 @@ StyledRect {
root.lock.pam.handleKey(event);
}
Behavior on implicitWidth {
Anim {}
}
StateLayer {
hoverEnabled: false
cursorShape: Qt.IBeamCursor
@ -47,44 +58,39 @@ StyledRect {
spacing: Tokens.spacing.medium
Item {
id: iconWrapper
Layout.fillHeight: true
implicitWidth: height
MaterialIcon {
id: fprintIcon
AnimLoader {
anchors.centerIn: parent
animate: true
text: {
if (root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries)
return "fingerprint_off";
if (root.lock.pam.fprint.active)
return "fingerprint";
return "lock";
}
color: root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries ? Colours.palette.m3error : Colours.palette.m3onSurface
opacity: root.lock.pam.passwd.active ? 0 : 1
anchors.verticalCenterOffset: sourceComponent === iconComp ? 1 : 0
sourceComp: root.lock.pam.passwd.active ? loadingComp : iconComp
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
Component {
id: iconComp
MaterialIcon {
animate: true
text: {
if (root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries)
return "fingerprint_off";
if (root.lock.pam.fprint.active)
return "fingerprint";
return "lock";
}
color: root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.medium.scale(root.centerScale).build()
}
}
Loader {
anchors.fill: parent
anchors.margins: Tokens.padding.small
Component {
id: loadingComp
active: opacity > 0
opacity: root.lock.pam.passwd.active ? 1 : 0
sourceComponent: LoadingIndicator {}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
LoadingIndicator {
implicitSize: iconWrapper.height - Tokens.padding.small * 2
}
}
}
@ -95,19 +101,45 @@ StyledRect {
Layout.fillWidth: true
Layout.fillHeight: true
centerScale: root.centerScale
pam: root.lock.pam
}
StyledRect {
Item {
id: enterButton
implicitWidth: implicitHeight
implicitHeight: enterIcon.implicitHeight + Tokens.padding.small
implicitHeight: {
const h = enterIcon.implicitHeight + Tokens.padding.extraSmall * 2;
return h % 2 === 0 ? h : h + 1;
}
color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
radius: Tokens.rounding.full
MaterialShape {
anchors.fill: parent
StateLayer {
color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
onClicked: root.lock.pam.passwd.start()
color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
shape: root.lock.pam.buffer ? MaterialShape.Arrow : MaterialShape.Circle
scale: !root.lock.pam.buffer ? 1 : mouse.pressed ? 0.6 : mouse.containsMouse ? 0.8 : 0.7
rotation: 90
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
Behavior on color {
CAnim {}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: root.lock.pam.buffer ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.lock.pam.buffer && root.lock.pam.passwd.start()
}
}
MaterialIcon {
@ -115,8 +147,15 @@ StyledRect {
anchors.centerIn: parent
text: "arrow_forward"
color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
fontStyle: Tokens.font.icon.size(Tokens.font.icon.large.pointSize).weight(Font.Medium).build()
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.medium.scale(root.centerScale * 1.2).build()
opacity: root.lock.pam.buffer ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}
}

View file

@ -119,7 +119,7 @@ Item {
opacity: root.stateMsgShouldBeVisible && !root.msg ? 1 : 0
color: Colours.palette.m3onSurfaceVariant
font: Tokens.font.mono.small
font: Tokens.font.body.small
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
lineHeight: 1.2
@ -145,7 +145,7 @@ Item {
opacity: 0
color: Colours.palette.m3error
font: Tokens.font.mono.small
font: Tokens.font.body.small
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere

View file

@ -16,7 +16,7 @@ Item {
onClose: root.close()
}
property color blobColour: Theme.panelBg // NoSignal: fixed dark-glass window surface
property color blobColour: Colours.tPalette.m3surfaceContainerLow
signal close
@ -30,7 +30,7 @@ Item {
BlobGroup {
id: blobGroup
smoothing: 0 // NoSignal: crisp corners (no organic blob smoothing)
smoothing: root.Tokens.rounding.medium
color: root.blobColour
}
@ -38,7 +38,7 @@ Item {
anchors.fill: parent
group: blobGroup
opacity: root.blobColour.a
radius: Theme.radius.panel // NoSignal: sharp 4px corners
radius: Tokens.rounding.large
borderLeft: navPane.width + navPane.anchors.margins * 2
borderRight: Tokens.padding.medium

View file

@ -73,20 +73,10 @@ QtObject {
// System
Component {
// Updates (NoSignal)
StackPage {
Component {
UpdatesPage {}
}
}
PlaceholderComp {}
},
Component {
// Additions (NoSignal)
StackPage {
Component {
AdditionsPage {}
}
}
PlaceholderComp {}
},
// Shell
@ -165,11 +155,11 @@ QtObject {
anchors.centerIn: parent
spacing: Tokens.padding.extraSmall
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "handyman"
text: "handyman"
color: Colours.palette.m3outlineVariant
fontStyle: Tokens.font.icon.extraLarge
font: Tokens.font.icon.extraLarge
}
StyledText {

View file

@ -50,9 +50,9 @@ QtObject {
category: "system"
},
{
label: qsTr("Additions"),
label: qsTr("Plugins"),
icon: "extension",
description: qsTr("Install optional software"),
description: qsTr("Manage plugins"),
category: "system"
},

View file

@ -58,13 +58,13 @@ ItemList {
radius: Tokens.rounding.full
color: device.active ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
NsIcon {
MaterialIcon {
id: devIcon
anchors.centerIn: parent
icon: root.iconName
text: root.iconName
color: device.active ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
fill: device.active ? 1 : 0
Behavior on fill {
@ -80,10 +80,10 @@ ItemList {
elide: Text.ElideRight
}
NsIcon {
icon: "check"
MaterialIcon {
text: "check"
color: Colours.palette.m3primary
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
opacity: device.active ? 1 : 0
Behavior on opacity {

View file

@ -1,73 +0,0 @@
// 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
}
}

View file

@ -7,10 +7,9 @@ StyledRect {
property bool first
property bool last
// NoSignal: dark-glass cards, 4px outer corners (connected-list look)
color: Theme.fillSubtle
topLeftRadius: first ? Theme.radius.panel : 0
topRightRadius: first ? Theme.radius.panel : 0
bottomLeftRadius: last ? Theme.radius.panel : 0
bottomRightRadius: last ? Theme.radius.panel : 0
color: Colours.tPalette.m3surfaceContainer
topLeftRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
bottomLeftRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
}

View file

@ -37,11 +37,11 @@ ConnectedRect {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.extraSmall
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: root.placeholderIcon
text: root.placeholderIcon
color: Colours.palette.m3outline
fontStyle: Tokens.font.icon.large
font: Tokens.font.icon.large
animate: true
}

View file

@ -10,7 +10,7 @@ import qs.modules.nexus.common
ConnectedRect {
id: root
property alias icon: icon.icon
property alias icon: icon.text
property alias label: label.text
property alias status: status.text
@ -32,11 +32,11 @@ ConnectedRect {
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
NsIcon {
MaterialIcon {
id: icon
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
}
ColumnLayout {
@ -63,10 +63,10 @@ ConnectedRect {
}
}
NsIcon {
icon: "chevron_right"
MaterialIcon {
text: "chevron_right"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
}
}
}

View file

@ -11,7 +11,7 @@ import qs.modules.nexus.common
ConnectedRect {
id: root
property alias icon: icon.icon
property alias icon: icon.text
property alias label: label.text
property alias valueLabel: valueLabel.text
property real value
@ -28,11 +28,11 @@ ConnectedRect {
anchors.topMargin: Tokens.padding.large
spacing: Tokens.spacing.medium
NsIcon {
MaterialIcon {
id: icon
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
}
ColumnLayout {

View file

@ -1,102 +0,0 @@
// 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
}
}

View file

@ -86,11 +86,11 @@ VerticalFadeFlickable {
radius: Tokens.rounding.full
color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
NsIcon {
MaterialIcon {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
icon: item.modelData.icon
text: item.modelData.icon
color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
grade: 25

View file

@ -37,8 +37,8 @@ StyledRect {
spacing: Tokens.spacing.small
NsIcon {
icon: "search"
MaterialIcon {
text: "search"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
}

View file

@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Caelestia
import Caelestia.Config
@ -62,27 +61,22 @@ PageBase {
width: parent.width - Tokens.padding.largeIncreased * 2
spacing: Tokens.spacing.small
Image {
AnimatedLogo {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 104
Layout.preferredHeight: 104
source: Quickshell.shellDir + "/assets/nosignal-logo.png"
sourceSize.width: 208
sourceSize.height: 208
fillMode: Image.PreserveAspectFit
smooth: true
Layout.preferredWidth: implicitWidth
Layout.preferredHeight: implicitHeight
}
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.small
text: "NoSignal"
text: "Caelestia"
font: Tokens.font.headline.builders.large.width(110).build()
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: "V1"
text: CUtils.version ? `v${CUtils.version}` : "…"
color: Colours.palette.m3onSurfaceVariant
font: Tokens.font.body.medium
}

View file

@ -1,120 +0,0 @@
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;
}
}
}
}

View file

@ -97,9 +97,9 @@ PageBase {
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
NsIcon {
icon: "tune"
fontStyle: Tokens.font.icon.medium
MaterialIcon {
text: "tune"
font: Tokens.font.icon.medium
}
ColumnLayout {
@ -123,10 +123,10 @@ PageBase {
}
}
NsIcon {
icon: "chevron_right"
MaterialIcon {
text: "chevron_right"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
}
}
}

View file

@ -93,13 +93,13 @@ PageBase {
radius: Tokens.rounding.full
color: device.connected ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
NsIcon {
MaterialIcon {
id: deviceIcon
anchors.centerIn: parent
icon: Icons.getBluetoothIcon(device.modelData?.icon ?? "")
text: Icons.getBluetoothIcon(device.modelData?.icon ?? "")
color: device.connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
fill: device.connected ? 1 : 0
opacity: device.textOpacity
@ -193,9 +193,9 @@ PageBase {
Anim {}
}
NsIcon {
icon: "add"
fontStyle: Tokens.font.icon.medium
MaterialIcon {
text: "add"
font: Tokens.font.icon.medium
}
StyledText {

View file

@ -92,71 +92,41 @@ PageBase {
text: qsTr("Weather")
}
// Location for the weather service. Weather.qml accepts a city name
// (geocoded) or a "lat,lon" string, and auto-detects via IP when empty.
// Placeholder until the map-based location picker lands
ConnectedRect {
Layout.fillWidth: true
first: true
last: true
implicitHeight: weatherLayout.implicitHeight + weatherLayout.anchors.margins * 2
implicitHeight: comingSoon.implicitHeight + Tokens.padding.extraLarge * 2
ColumnLayout {
id: weatherLayout
id: comingSoon
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: Tokens.padding.largeIncreased
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.small
anchors.centerIn: parent
width: parent.width - Tokens.padding.largeIncreased * 2
spacing: Tokens.padding.extraSmall
StyledText {
Layout.fillWidth: true
text: qsTr("Location")
font: Tokens.font.body.small
elide: Text.ElideRight
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "map"
color: Colours.palette.m3outlineVariant
font: Tokens.font.icon.extraLarge
}
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 {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Location picker coming soon")
color: Colours.palette.m3outlineVariant
font: Tokens.font.title.small
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: qsTr("Leave empty to detect your location automatically.")
color: Colours.palette.m3outline
font: Tokens.font.label.small
text: qsTr("Choose your weather location on a map in a future update")
color: Colours.palette.m3outlineVariant
font: Tokens.font.body.small
}
}
}

View file

@ -131,10 +131,10 @@ PageBase {
anchors.rightMargin: Tokens.padding.extraLarge
spacing: Tokens.spacing.medium
NsIcon {
icon: Icons.getNetworkIcon(network.modelData.strength)
MaterialIcon {
text: Icons.getNetworkIcon(network.modelData.strength)
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
@ -165,10 +165,10 @@ PageBase {
Component {
id: iconComp
NsIcon {
icon: network.modelData.active ? "settings" : "lock"
MaterialIcon {
text: network.modelData.active ? "settings" : "lock"
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
}
@ -218,9 +218,9 @@ PageBase {
spacing: Tokens.spacing.medium
NsIcon {
icon: "add"
fontStyle: Tokens.font.icon.medium
MaterialIcon {
text: "add"
font: Tokens.font.icon.medium
}
StyledText {

View file

@ -211,16 +211,6 @@ PageBase {
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 {
Layout.fillWidth: true
last: true

View file

@ -1,139 +0,0 @@
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;
}
}
}
}

View file

@ -46,11 +46,11 @@ PageBase {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.extraSmall
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "hide_image"
text: "hide_image"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge
font: Tokens.font.icon.extraLarge
}
StyledText {

View file

@ -125,10 +125,10 @@ PageBase {
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
NsIcon {
icon: Icons.getBluetoothIcon(newDevice.modelData?.icon ?? "")
MaterialIcon {
text: Icons.getBluetoothIcon(newDevice.modelData?.icon ?? "")
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
opacity: newDevice.textOpacity
}

View file

@ -72,11 +72,11 @@ PageBase {
anchors.centerIn: parent
spacing: 0
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "delete"
text: "delete"
color: forgetBtn.onColour
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
}
StyledText {
@ -126,11 +126,11 @@ PageBase {
ColumnLayout {
spacing: 0
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: root.connected ? "close" : "add"
text: root.connected ? "close" : "add"
color: connectBtn.inactiveOnColour
fontStyle: Tokens.font.icon.medium
font: Tokens.font.icon.medium
animate: true
}

View file

@ -22,11 +22,11 @@ PageBase {
anchors.centerIn: parent
spacing: Tokens.padding.extraSmall
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "handyman"
text: "handyman"
color: Colours.palette.m3outlineVariant
fontStyle: Tokens.font.icon.extraLarge
font: Tokens.font.icon.extraLarge
}
StyledText {

View file

@ -168,9 +168,9 @@ PageBase {
anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall
NsIcon {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "hide_image"
text: "hide_image"
color: Colours.palette.m3outline
fontStyle: Tokens.font.icon.extraLarge
}

View file

@ -63,7 +63,7 @@ Item {
anchors.rightMargin: root.clampedPadding
color: "transparent"
radius: 0
radius: Tokens.rounding.large
StyledListView {
id: list

View file

@ -23,7 +23,7 @@ StyledRect {
property bool expanded: Config.notifs.openExpanded
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer
radius: 0
radius: Tokens.rounding.large
implicitHeight: inner.implicitHeight
@ -116,7 +116,7 @@ StyledRect {
visible: root.hasImage || root.hasAppIcon
sourceComponent: StyledClippingRect {
radius: 0
radius: Tokens.rounding.full
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
implicitHeight: TokenConfig.sizes.notifs.image
@ -147,7 +147,7 @@ StyledRect {
anchors.bottom: root.hasImage ? image.bottom : undefined
sourceComponent: StyledRect {
radius: 0
radius: Tokens.rounding.full
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
implicitHeight: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
@ -177,8 +177,8 @@ StyledRect {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
sourceComponent: NsIcon {
icon: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
sourceComponent: MaterialIcon {
text: 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
fontStyle: Tokens.font.icon.medium
}
@ -358,17 +358,17 @@ StyledRect {
implicitHeight: expandIcon.implicitHeight
StateLayer {
radius: 0
radius: Tokens.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
onClicked: root.expanded = !root.expanded
}
NsIcon {
MaterialIcon {
id: expandIcon
anchors.centerIn: parent
anchors.verticalCenterOffset: root.expanded ? -1 : 1
icon: "expand_more"
text: "expand_more"
fontStyle: Tokens.font.icon.medium
rotation: root.expanded ? 180 : 0

View file

@ -1,46 +0,0 @@
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()
}
}

View file

@ -1,260 +0,0 @@
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
}
}
}
}

View file

@ -1,166 +0,0 @@
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 {}
}
}
}
}

View file

@ -1,85 +0,0 @@
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}`)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more