Compare commits

...
Sign in to create a new pull request.

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
25 changed files with 308 additions and 138 deletions

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",

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

@ -48,12 +48,11 @@ ButtonBase {
id: iconLabel
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;
}
@ -68,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

View file

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

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

@ -23,7 +23,7 @@ StyledRect {
id: layout
anchors.centerIn: parent
spacing: Tokens.spacing.small
spacing: Tokens.spacing.extraSmall
Loader {
Layout.alignment: Qt.AlignHCenter

View file

@ -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

@ -189,7 +189,11 @@ Item {
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
}

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,7 +30,7 @@ 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
@ -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

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

@ -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

@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.services

View file

@ -188,6 +188,8 @@ class AppearanceFont : public ConfigObject {
CONFIG_SUBOBJECT(FontStyleConfig, mono)
CONFIG_SUBOBJECT(IconFontStyleConfig, icon)
CONFIG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
// Google Sans Flex doesn't play well with unicode symbols apparently, so use Rubik instead
CONFIG_PROPERTY(QString, workspaces, QStringLiteral("Rubik"))
public:
explicit AppearanceFont(QObject* parent = nullptr)

View file

@ -209,6 +209,10 @@ FontBuilder FontTokens::clock() const {
return FontBuilder(m_clock);
}
QString FontTokens::workspaces() const {
return m_font ? m_font->workspaces() : QString();
}
void FontTokens::bindFont(AppearanceFont* font) {
if (m_font == font)
return;
@ -229,6 +233,7 @@ void FontTokens::bindFont(AppearanceFont* font) {
connect(font, &AppearanceFont::clockChanged, this, &FontTokens::rebuildClock);
connect(font, &AppearanceFont::scaleChanged, this, &FontTokens::rebuildScale);
connect(font, &AppearanceFont::workspacesChanged, this, &FontTokens::workspacesChanged);
} else {
rebuildScale();
m_headline->bind(nullptr);
@ -240,6 +245,7 @@ void FontTokens::bindFont(AppearanceFont* font) {
}
rebuildClock();
emit workspacesChanged();
}
void FontTokens::rebuildScale() {

View file

@ -131,6 +131,7 @@ class FontTokens : public QObject {
Q_PROPERTY(caelestia::config::FontStyle* mono READ mono CONSTANT FINAL)
Q_PROPERTY(caelestia::config::IconFontStyle* icon READ icon CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontBuilder clock READ clock NOTIFY clockChanged FINAL)
Q_PROPERTY(QString workspaces READ workspaces NOTIFY workspacesChanged FINAL)
public:
explicit FontTokens(QObject* parent = nullptr);
@ -144,9 +145,11 @@ public:
[[nodiscard]] FontStyle* mono() const;
[[nodiscard]] IconFontStyle* icon() const;
[[nodiscard]] FontBuilder clock() const;
[[nodiscard]] QString workspaces() const;
signals:
void clockChanged();
void workspacesChanged();
private:
void rebuildClock();

View file

@ -285,7 +285,7 @@ class LockTokens : public ConfigObject {
CONFIG_PROPERTY(qreal, heightMult, 0.7)
CONFIG_PROPERTY(qreal, ratio, 16.0 / 9.0)
CONFIG_PROPERTY(int, centerWidth, 600)
CONFIG_PROPERTY(int, showWeatherDetailsHeight, 500)
CONFIG_PROPERTY(int, showWeatherDetailsHeight, 550)
CONFIG_PROPERTY(int, showForecastHeight, 975)
CONFIG_PROPERTY(int, forecastItemWidth, 51)
CONFIG_PROPERTY(int, largeLogoWidth, 320)

View file

@ -16,7 +16,8 @@ class UserPaths : public ConfigObject {
CONFIG_GLOBAL_PROPERTY(
QString, wallpaperDir, QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + u"/Wallpapers"_s)
CONFIG_GLOBAL_PROPERTY(QString, lyricsDir, QDir::homePath() + u"/Music/lyrics/"_s)
CONFIG_GLOBAL_PROPERTY(
QString, lyricsDir, QStandardPaths::writableLocation(QStandardPaths::MusicLocation) + u"/Lyrics/"_s)
CONFIG_PROPERTY(QString, sessionGif, u"root:/assets/kurukuru.gif"_s)
CONFIG_PROPERTY(QString, mediaGif, u"root:/assets/bongocat.gif"_s)
CONFIG_PROPERTY(QString, noNotifsPic, u"root:/assets/dino.png"_s)

View file

@ -788,7 +788,6 @@ void Lyrics::onPreferredBackendConfigChanged() {
}
void Lyrics::onLyricsDirChanged() {
loadLyricsMap();
scheduleLoad();
}
@ -828,11 +827,7 @@ void Lyrics::persistTrackPrefs() {
}
m_lyricsMap.insert(key, entry);
const QString dir = lyricsDir();
if (dir.isEmpty()) {
return;
}
QDir().mkpath(dir);
QDir().mkpath(stateDir());
QSaveFile out(lyricsMapPath());
if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
@ -855,6 +850,11 @@ QString Lyrics::lyricsDir() const {
if (dir.isEmpty()) {
return {};
}
if (dir == u"~"_s) {
dir = QDir::homePath();
} else if (dir.startsWith(u"~/"_s)) {
dir.replace(0, 1, QDir::homePath());
}
while (dir.endsWith(QLatin1Char('/')) && dir.size() > 1) {
dir.chop(1);
}
@ -862,8 +862,7 @@ QString Lyrics::lyricsDir() const {
}
QString Lyrics::lyricsMapPath() const {
const QString dir = lyricsDir();
return dir.isEmpty() ? QString() : dir + u"/lyrics_map.json"_s;
return stateDir() + u"/lyrics_map.json"_s;
}
QString Lyrics::trackKey() const {
@ -876,30 +875,41 @@ QString Lyrics::trackKey() const {
QString Lyrics::backendKey(LyricsBackend::Backend value) {
switch (value) {
case LyricsBackend::Local:
return u"LyricsBackend::Local"_s;
return u"Local"_s;
case LyricsBackend::LRCLIB:
return u"LyricsBackend::LRCLIB"_s;
return u"LRCLIB"_s;
case LyricsBackend::NetEase:
return u"LyricsBackend::NetEase"_s;
return u"NetEase"_s;
case LyricsBackend::Auto:
default:
return u"LyricsBackend::Auto"_s;
return u"Auto"_s;
}
}
LyricsBackend::Backend Lyrics::backendFromKey(const QString& key) {
if (key.compare(u"LyricsBackend::Local"_s, Qt::CaseInsensitive) == 0) {
if (key.compare(u"Local"_s, Qt::CaseInsensitive) == 0) {
return LyricsBackend::Local;
}
if (key.compare(u"LyricsBackend::LRCLIB"_s, Qt::CaseInsensitive) == 0) {
if (key.compare(u"LRCLIB"_s, Qt::CaseInsensitive) == 0) {
return LyricsBackend::LRCLIB;
}
if (key.compare(u"LyricsBackend::NetEase"_s, Qt::CaseInsensitive) == 0) {
if (key.compare(u"NetEase"_s, Qt::CaseInsensitive) == 0) {
return LyricsBackend::NetEase;
}
return LyricsBackend::Auto;
}
const QString& Lyrics::stateDir() {
static const QString s_dir = [] {
QString state = qEnvironmentVariable("XDG_STATE_HOME");
if (state.isEmpty()) {
state = QDir::homePath() + u"/.local/state"_s;
}
return state + u"/caelestia/lyrics"_s;
}();
return s_dir;
}
const QString& Lyrics::cacheDir() {
static const QString s_dir = [] {
QString cache = qEnvironmentVariable("XDG_CACHE_HOME");

View file

@ -107,6 +107,7 @@ private:
[[nodiscard]] static QString backendKey(LyricsBackend::Backend value);
[[nodiscard]] static LyricsBackend::Backend backendFromKey(const QString& key);
[[nodiscard]] static const QString& stateDir();
[[nodiscard]] static const QString& cacheDir();
[[nodiscard]] static QString cachePathFor(LyricsBackend::Backend backend, const QString& id);
[[nodiscard]] static QString readCachedLrc(LyricsBackend::Backend backend, const QString& id);

View file

@ -20,6 +20,7 @@ Searcher {
property string previewPath
property string actualCurrent
property bool previewColourLock
property bool pendingPreviewClear
function getCategoryFor(w: FileSystemEntry): string {
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
@ -47,7 +48,14 @@ Searcher {
function stopPreview(): void {
showPreview = false;
if (!previewColourLock)
if (previewColourLock)
pendingPreviewClear = true;
else
Colours.showPreview = false;
}
onPreviewColourLockChanged: {
if (!previewColourLock && pendingPreviewClear)
Colours.showPreview = false;
}