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 easeStandard: [0.05, 0.9, 0.1, 1.0] readonly property list 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 }