253 lines
14 KiB
QML
253 lines
14 KiB
QML
pragma Singleton
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Tokyo Night Moon — the single source of truth for every color, radius,
|
|
// duration and font in the shell. Nothing below this file should hardcode a
|
|
// color; if you find yourself writing "#" outside this file, add a token here.
|
|
//
|
|
// The bar keeps GNOME's 36px information density but sits directly on the
|
|
// desktop, edge to edge. The dock retains the former glass vocabulary: bottom,
|
|
// 48px icons and dot indicators.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
import Quickshell
|
|
// QtQuick is required even though nothing visual is declared here: `color` is a
|
|
// QtQuick value type, and Qt.rgba() lives in its JS namespace.
|
|
import QtQuick
|
|
import qs.services
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
// ── Color scheme ───────────────────────────────────────────────────────
|
|
// The scheme still exists as a boolean because alphas and a handful of
|
|
// mix directions depend on which side of the ground the theme sits — but
|
|
// the palette itself now comes from the active theme record.
|
|
//
|
|
// Every token below is a binding, so selecting a theme repaints the whole
|
|
// shell without anything needing to know it happened.
|
|
readonly property bool dark: DesktopPreferences.get("colorScheme") !== "light"
|
|
|
|
// ── Palette ─────────────────────────────────────────────────────────────
|
|
// The active theme's palette — ThemeProfiles resolves it and it is always
|
|
// complete: a shipped theme carries its own, a custom theme without one
|
|
// inherits its scheme's default (Tokyo Moon or Tokyo Day) from the
|
|
// catalog. Nothing here needs a per-token fallback.
|
|
readonly property var palette: ThemeProfiles.activePalette
|
|
|
|
readonly property color bg: root.palette.bg
|
|
readonly property color bgDark: root.palette.bgDark
|
|
readonly property color bgHighlight: root.palette.bgHighlight
|
|
readonly property color bgPanel: root.palette.bgPanel // dock glass surface
|
|
readonly property color bgPopover: root.palette.bgPopover // Openbar submenu background
|
|
|
|
readonly property color fg: root.palette.fg
|
|
readonly property color fgDim: root.palette.fgDim
|
|
readonly property color fgMuted: root.palette.fgMuted
|
|
readonly property color gutter: root.palette.gutter
|
|
|
|
// ── Bar text ────────────────────────────────────────────────────────────
|
|
// Every other surface in the shell draws on a ground the theme chose. The
|
|
// bar draws on the wallpaper, which the theme has never seen — so a
|
|
// photograph with a bright sky can leave the palette's own fg unreadable
|
|
// exactly where the clock is.
|
|
//
|
|
// Hence a second neutral family, used by the bar and nowhere else. Left
|
|
// alone it *is* the fg family, so nothing changes for anyone who never
|
|
// asks. Forced light or dark, it becomes a run that survives whatever is
|
|
// underneath it, and the two dims are mixed toward the opposite end rather
|
|
// than picked by hand, so the three stay a family either way.
|
|
readonly property string barTextTone: DesktopPreferences.get("barTextTone")
|
|
|
|
readonly property color barFg: {
|
|
if (root.barTextTone === "light")
|
|
return "#f4f6ff";
|
|
if (root.barTextTone === "dark")
|
|
return "#1b2130";
|
|
return root.fg;
|
|
}
|
|
|
|
readonly property color barFgDim: {
|
|
if (root.barTextTone === "light")
|
|
return root.mix(root.barFg, "#20242f", 0.35);
|
|
if (root.barTextTone === "dark")
|
|
return root.mix(root.barFg, "#ffffff", 0.35);
|
|
return root.fgDim;
|
|
}
|
|
|
|
readonly property color barFgMuted: {
|
|
if (root.barTextTone === "light")
|
|
return root.mix(root.barFg, "#20242f", 0.5);
|
|
if (root.barTextTone === "dark")
|
|
return root.mix(root.barFg, "#ffffff", 0.5);
|
|
return root.fgMuted;
|
|
}
|
|
|
|
// ── The accent ──────────────────────────────────────────────────────────
|
|
//
|
|
// `accent` is the primary and carries every state meaning (focused, active,
|
|
// on). `accentSecondary` never appears alone, only as the far end of a
|
|
// gradient. That restraint is the whole point: the two colors meeting is
|
|
// the signature, so the second color stops being special the moment it is
|
|
// used as a flat fill.
|
|
//
|
|
// NAMED accents rather than a free color. Each name carries a curated
|
|
// triple per scheme, because an arbitrary hex cannot work in both: a color
|
|
// legible on the Moon background is usually illegible on the Day one, and a
|
|
// picker that lets someone choose an unreadable desktop is not a feature.
|
|
// Every pair below measures at least 3:1 against the ground it sits on.
|
|
// This is also GNOME's model, which is the parity being chased.
|
|
//
|
|
// Blue is the shipped Prism -- blue leading, orchid following -- and stays
|
|
// the default.
|
|
//
|
|
// The table itself lives in services/ThemeProfileModel.js so a curated
|
|
// accent and a custom profile are the same kind of record. Its `gnome`
|
|
// member is the nearest name in GNOME's own accent-color enum, which is a
|
|
// fixed list of nine we do not get to extend; it is what libadwaita
|
|
// applications -- Files, Papers, Loupe -- are told to use, so choosing an
|
|
// accent here recolors them too instead of leaving them in GNOME blue.
|
|
readonly property var accents: ThemeProfiles.curatedAccents
|
|
readonly property var activeProfile: ThemeProfiles.activeProfile
|
|
|
|
// ThemeProfiles validates every persisted record before it can become
|
|
// active, so these bindings are both reactive and safe to expose as the
|
|
// shell-wide colour roles.
|
|
readonly property color accent: root.activeProfile.accent
|
|
readonly property color accentSecondary: root.activeProfile.secondary
|
|
readonly property color accentAlt: root.palette.accentAlt
|
|
readonly property color cyan: root.palette.cyan
|
|
readonly property color teal: root.palette.teal
|
|
readonly property color green: root.palette.green
|
|
readonly property color yellow: root.palette.yellow
|
|
readonly property color orange: root.palette.orange
|
|
readonly property color red: root.palette.red
|
|
readonly property color redDeep: root.palette.redDeep
|
|
readonly property color magenta: root.palette.magenta
|
|
readonly property color pink: root.palette.pink
|
|
|
|
// Semantic aliases — prefer these in widgets so intent survives a repaint.
|
|
readonly property color ok: green
|
|
readonly property color warn: yellow
|
|
readonly property color danger: red
|
|
readonly property color urgent: orange
|
|
|
|
// ── Surface alphas ──────────────────────────────────────────────────────
|
|
// The dock sits on compositor blur, so its fill is deliberately very
|
|
// translucent. Popovers are near-opaque because they carry text that must
|
|
// stay legible. The bar itself has no surface or alpha token.
|
|
// Light surfaces need more opacity for the same sense of a solid panel: the
|
|
// same 0.34 that reads as glass over a dark desktop reads as haze over a
|
|
// light one, and text on it stops being legible.
|
|
readonly property real dockAlpha: root.dark ? 0.34 : 0.62
|
|
readonly property real popoverAlpha: root.dark ? 0.92 : 0.97
|
|
|
|
// Toasts sit a little lighter than a popover you opened on purpose. A
|
|
// notification arrives unbidden over whatever you were doing, and at full
|
|
// popover weight it reads as a dialog demanding an answer -- but the 6%
|
|
// foreground tint it used to have left the text competing with the desktop
|
|
// behind it. This is the point between the two.
|
|
readonly property real toastAlpha: root.dark ? 0.86 : 0.94
|
|
readonly property real overlayAlpha: root.dark ? 0.55 : 0.40
|
|
readonly property real hoverAlpha: root.dark ? 0.14 : 0.10
|
|
readonly property real activeAlpha: root.dark ? 0.24 : 0.18
|
|
|
|
// ── Geometry ────────────────────────────────────────────────────────────
|
|
readonly property int barHeight: 36
|
|
readonly property int barGap: 6 // breathing room below the bar for popovers
|
|
readonly property int barSideMargin: 10 // inset for floating popovers
|
|
|
|
readonly property int dockIconSize: DesktopPreferences.get("dockIconSize")
|
|
readonly property int dockPadding: 8
|
|
readonly property int dockGap: 8
|
|
readonly property int dockRadius: 20
|
|
|
|
readonly property int popoverRadius: 18
|
|
readonly property int popoverPadding: 14
|
|
readonly property int popoverWidth: 380
|
|
readonly property int controlCenterWidth: 430
|
|
|
|
readonly property int cardRadius: 12
|
|
readonly property int pillRadius: 999
|
|
readonly property int itemSpacing: 8
|
|
readonly property int sectionSpacing: 14
|
|
|
|
// ── Type ────────────────────────────────────────────────────────────────
|
|
// Adwaita Sans for everything the user reads as text. No exceptions: a
|
|
// monospaced clock or percentage reads as a terminal readout pasted into a
|
|
// UI, which is the opposite of the intent here.
|
|
readonly property string fontFamily: DesktopPreferences.get("interfaceFont")
|
|
|
|
// Nerd Font, used ONLY to draw icon glyphs — never for text. It is the
|
|
// pragmatic alternative to freedesktop symbolic icons, which ship with a
|
|
// hardcoded dark fill Qt will not recolor (see widgets/ThemedIcon.qml).
|
|
readonly property string fontMono: DesktopPreferences.get("iconFont")
|
|
|
|
// Apply to any text whose digits change in place — clocks, percentages,
|
|
// elapsed timers, dimension readouts. Tabular figures all share one
|
|
// advance width, so the text stops twitching as the numbers tick without
|
|
// needing a monospaced face.
|
|
//
|
|
// font.family: Theme.fontFamily
|
|
// font.features: Theme.tabularFigures
|
|
readonly property var tabularFigures: ({
|
|
"tnum": 1
|
|
})
|
|
// The four sizes are a scale, not four independent numbers: they move
|
|
// together so the relationship between body, caption, heading and title
|
|
// survives a change to the base rather than drifting apart.
|
|
readonly property int fontSize: DesktopPreferences.get("interfaceFontSize")
|
|
readonly property int fontSizeSmall: Math.max(9, root.fontSize - 2)
|
|
readonly property int fontSizeLarge: root.fontSize + 3
|
|
readonly property int fontSizeTitle: root.fontSize + 7
|
|
|
|
// ── Motion ──────────────────────────────────────────────────────────────
|
|
// Event-driven only. Nothing in this shell animates while idle — no pulse,
|
|
// no shimmer, no spinners. These durations are used for open/close/hover.
|
|
//
|
|
// All of them collapse to zero when Reduce motion is on: the Accessibility
|
|
// toggle used to still the compositor's windows while the shell's own bar,
|
|
// dock and panels kept moving, which made it a half-truth. A duration of 0
|
|
// is a completed animation, so every Behavior and NumberAnimation in the
|
|
// shell obeys the switch without knowing it exists.
|
|
readonly property bool motionEnabled: Settings.animationsEnabled
|
|
readonly property int durFast: motionEnabled ? 120 : 0
|
|
readonly property int durNormal: motionEnabled ? 200 : 0
|
|
readonly property int durSlow: motionEnabled ? 320 : 0
|
|
|
|
// The dock revealing is the one animation that answers a live pointer
|
|
// movement, so it gets its own (much shorter) duration. Anything slower
|
|
// reads as the desktop lagging behind the cursor rather than as motion.
|
|
readonly property int durDockReveal: motionEnabled ? 90 : 0
|
|
|
|
// Matches the "snappy" spring curve defined in hypr/looks.lua.
|
|
readonly property list<real> easeStandard: [0.05, 0.9, 0.1, 1.0]
|
|
readonly property list<real> easeOvershoot: [0.34, 1.56, 0.64, 1.0]
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
// Qt.alpha() does not exist; this is the idiomatic way to re-alpha a token.
|
|
function alpha(c: color, a: real): color {
|
|
return Qt.rgba(c.r, c.g, c.b, a);
|
|
}
|
|
|
|
// Blend two tokens — used for hover/pressed states so we never invent a
|
|
// color that isn't derived from the palette.
|
|
function mix(a: color, b: color, ratio: real): color {
|
|
return Qt.rgba(a.r + (b.r - a.r) * ratio, a.g + (b.g - a.g) * ratio, a.b + (b.b - a.b) * ratio, a.a + (b.a - a.a) * ratio);
|
|
}
|
|
|
|
// ── The prism ───────────────────────────────────────────────────────────
|
|
// Where the two accents meet. Used for the hairline along the top edge of
|
|
// every glass surface (widgets/PrismEdge.qml), the focused window border,
|
|
// the active workspace pill and slider fills.
|
|
//
|
|
// Where the gradient runs left-to-right the blue leads, because the eye
|
|
// reads left first and blue is the color that carries meaning.
|
|
readonly property real prismStart: 0.22
|
|
readonly property real prismEnd: 0.78
|
|
|
|
// Opacity of the hairline itself. Deliberately below 1: at full strength it
|
|
// reads as a drawn line rather than as light catching an edge.
|
|
readonly property real prismEdgeAlpha: 0.85
|
|
}
|