Merge branch 'quattro' into unified-input-method-setup

This commit is contained in:
David Heinemeier Hansson committed 2026-10-09 13:19:17 +02:00
commit d040abb66f
246 files changed
+4592 -652

No files matched your search

+7 -6
View File
@@ -1,5 +1,6 @@
pragma Singleton
import QtQuick
import qs.Commons as Commons
import "BorderGeometry.js" as Geometry
// Central border-spec factory for shell surfaces and controls. A spec carries
@@ -22,7 +23,7 @@ QtObject {
}
function value(section, key) {
var v = Color.shellValues[section + "." + key]
var v = Commons.Color.shellValues[section + "." + key]
return (v === undefined || v === null) ? "" : v
}
@@ -39,7 +40,7 @@ QtObject {
var seen = {}
while (s.match(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/) && !seen[s]) {
seen[s] = true
var next = Color.shellValues[s]
var next = Commons.Color.shellValues[s]
if (next === undefined || next === null || String(next).length === 0) break
s = String(next).replace(/^\s+|\s+$/g, "")
}
@@ -68,10 +69,10 @@ QtObject {
var s = String(color || "").replace(/^\s+|\s+$/g, "")
var role = s.toLowerCase()
if (role === "foreground" || role === "text") return cssColor(Color.foreground, a)
if (role === "accent") return cssColor(Color.accent, a)
if (role === "urgent") return cssColor(Color.urgent, a)
if (role === "background") return cssColor(Color.background, a)
if (role === "foreground" || role === "text") return cssColor(Commons.Color.foreground, a)
if (role === "accent") return cssColor(Commons.Color.accent, a)
if (role === "urgent") return cssColor(Commons.Color.urgent, a)
if (role === "background") return cssColor(Commons.Color.background, a)
if (role === "transparent") return "transparent"
return Geometry.canonicalColor(s, a)
}
+2 -4
View File
@@ -128,11 +128,9 @@ QtObject {
property color borderError: root.composed("lock.border-error", "lock.border-alpha", root.urgent, 1.0)
property color selection: root.composed("lock.selection", "lock.selection-alpha", root.accent, 0.45)
}
// The image picker has no card surface; `scrim` is the full-screen dim
// wash, and per-slice dim overlays / text outlines use the foundational
// `background` color directly.
// The image picker has no card surface or backdrop; per-slice dim
// overlays / text outlines use the foundational `background` color directly.
readonly property QtObject imagePicker: QtObject {
property color scrim: root.composed("image-picker.scrim", "image-picker.scrim-alpha", root.background, 0.5)
property color text: root.pick("image-picker.text", root.foreground)
property color selectedBorder: root.composed("image-picker.selected-border", "image-picker.selected-border-alpha", root.accent, 1.0)
property color unselectedBorder: root.composed("image-picker.unselected-border", "image-picker.unselected-border-alpha", root.foreground, 0.28)
+22 -21
View File
@@ -1,5 +1,6 @@
pragma Singleton
import QtQuick
import qs.Commons as Commons
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
@@ -114,27 +115,27 @@ QtObject {
}
function resolveStateColor(token, foreground, accent, urgent, fallback) {
var fb = fallback || foreground || Color.foreground
var fb = fallback || foreground || Commons.Color.foreground
var s = String(token || "").replace(/^\s+|\s+$/g, "")
var role = s.toLowerCase()
if (role === "foreground" || role === "text") return foreground || Color.foreground
if (role === "accent") return accent || Color.accent
if (role === "urgent") return urgent || Color.urgent
if (role === "background") return Color.background
if (role === "foreground" || role === "text") return foreground || Commons.Color.foreground
if (role === "accent") return accent || Commons.Color.accent
if (role === "urgent") return urgent || Commons.Color.urgent
if (role === "background") return Commons.Color.background
if (role === "transparent") return Qt.rgba(0, 0, 0, 0)
return colorFromHex(s, fb)
}
function normalStateColor(foreground, accent, urgent) {
return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function hoverStateColor(foreground, accent, urgent) {
return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function selectedStateColor(foreground, accent, urgent) {
return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function pressedStateColor(foreground, accent, urgent) {
@@ -149,7 +150,7 @@ QtObject {
}
function selectionStateColor(foreground, accent, urgent) {
return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function normalFillFor(foreground, accent, urgent) { return Util.alpha(normalStateColor(foreground, accent, urgent), normalFillAlpha) }
@@ -187,17 +188,17 @@ QtObject {
}
// Convenience colors resolved against the foundational palette.
readonly property color normalFill: normalFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color hoverFill: hoverFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedFill: selectedFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color pressedFill: pressedFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color focusFillColor: focusFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color normalBorderColor: normalBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color hoverBorderColor: hoverBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedBorderColor: selectedBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color focusBorderColor: focusBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedAccentFill: Util.alpha(Color.accent, selectedFillAlpha)
readonly property color selectionFill: selectionFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color normalFill: normalFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color hoverFill: hoverFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedFill: selectedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color pressedFill: pressedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color focusFillColor: focusFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color normalBorderColor: normalBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color hoverBorderColor: hoverBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedBorderColor: selectedBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color focusBorderColor: focusBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedAccentFill: Util.alpha(Commons.Color.accent, selectedFillAlpha)
readonly property color selectionFill: selectionFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
// ---------------------------------------------------------- spacing
//
@@ -390,7 +391,7 @@ QtObject {
}
// Pull typography, bar dimensions, state tokens, and spacing out of the
// shell.toml dict that Color already parsed. Called by Color.loadShell so
// shell.toml dict that Color already parsed. Called by Commons.Color.loadShell so
// a single parse pass feeds both singletons.
function applyShellValues(values) {
var fontOut = {}
+7 -6
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// The button. One component for every clickable thing in the kit.
// States compose independently and are applied in priority order:
@@ -32,9 +33,9 @@ BorderSurface {
property bool bordered: false
// Colors. Defaults track the theme; per-instance overrides are honored.
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color background: "transparent"
property color accent: Color.accent
property color accent: Commons.Color.accent
// Sizing.
property string fontFamily: Style.font.family
@@ -54,9 +55,9 @@ BorderSurface {
// Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull
// from [tooltip] in shell.toml; override per-instance only when a button
// intentionally wants a tooltip that diverges from the theme.
property color tooltipBackground: Color.tooltip.background
property color tooltipForeground: Color.tooltip.text
property color tooltipBorder: Color.tooltip.border
property color tooltipBackground: Commons.Color.tooltip.background
property color tooltipForeground: Commons.Color.tooltip.text
property color tooltipBorder: Commons.Color.tooltip.border
signal clicked()
signal rightClicked()
@@ -77,7 +78,7 @@ BorderSurface {
readonly property bool hot: mouseArea.containsMouse || hasCursor
readonly property bool _showFocusRing: focusable && activeFocus
readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent)
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Commons.Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _focusBorderSpec: Border.controlSpec("focus", root.foreground, root.accent)
readonly property var _hoverBorderSpec: Border.controlSpec("hover-cursor", root.foreground, root.accent)
readonly property var _selectedBorderSpec: Border.controlSpec("selected", root.foreground, root.accent)
+4 -3
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Mutually-exclusive row of Buttons — the form-style "pick one of N"
// pattern (bar position top/right/bottom/left, theme preset chips, etc.).
@@ -25,9 +26,9 @@ Row {
property var options: []
property string value: ""
property color foreground: Color.foreground
property color background: Color.background
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color background: Commons.Color.background
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property bool focusable: true
+9 -8
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -9,11 +10,11 @@ Item {
property string cancelText: "Cancel"
property string confirmText: "Confirm"
property int selectedIndex: 1
property color background: Color.background
property color foreground: Color.foreground
property color scrim: Util.alpha(Color.background, 0.7)
property color selectedBackground: Util.alpha(Color.foreground, 0.08)
property color selectedText: Color.accent
property color background: Commons.Color.background
property color foreground: Commons.Color.foreground
property color scrim: Util.alpha(Commons.Color.background, 0.7)
property color selectedBackground: Util.alpha(Commons.Color.foreground, 0.08)
property color selectedText: Commons.Color.accent
property string fontFamily: Style.font.family
property int cornerRadius: Style.cornerRadius
@@ -98,10 +99,10 @@ Item {
width: Style.space(88)
height: Style.space(34)
color: selected
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
? (destructive ? Util.alpha(Commons.Color.urgent, 0.22) : root.selectedBackground)
: "transparent"
borderSpec: Border.flat(destructive
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
? (selected ? Commons.Color.urgent : Util.alpha(Commons.Color.urgent, 0.56))
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)), Style.normalBorderWidth)
radius: 0
@@ -109,7 +110,7 @@ Item {
textFormat: Text.PlainText
anchors.centerIn: parent
text: modelData
color: destructive ? (selected ? Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
color: destructive ? (selected ? Commons.Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Shared visual chrome for keyboard-and-mouse-navigable items inside a panel.
// Contract: items must NOT read `containsMouse` for color/border. Mouse
@@ -19,8 +20,8 @@ BorderSurface {
property bool outline: false
property bool bordered: false
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property color fill: Style.hoverFillFor(foreground, accent)
property color currentFill: Style.selectedFillFor(foreground, accent)
+8 -7
View File
@@ -1,10 +1,11 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Themed single-select dropdown. Trigger row paints with the kit's focus
// chrome; the popup anchors below and uses Color.popups.background +
// Color.popups.border so it reads as a panel surface rather than the
// chrome; the popup anchors below and uses Commons.Color.popups.background +
// Commons.Color.popups.border so it reads as a panel surface rather than the
// platform-native ComboBox look.
//
// `options` accepts either a plain string[] or an array of
@@ -22,11 +23,11 @@ Item {
property string value: ""
property var options: []
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+3 -2
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
// Layer-shell popup attached to a bar widget icon, designed for
// click-driven AND keyboard-driven panels (e.g. SUPER+CTRL+W summon).
@@ -44,7 +45,7 @@ PanelWindow {
property int padding: Style.spacing.popupPadding
property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200)
property var borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
property var borderSpec: Border.surfaceSpec("popups", "border", Commons.Color.popups.border, Math.max(1, Style.space(2)))
property bool centerOnBar: false
property bool open: false
property int gap: Style.gapsOut // distance between bar edge and panel
@@ -382,7 +383,7 @@ PanelWindow {
y: root.cardOrigin.y
width: root.contentWidth
height: root.contentHeight
color: Color.popups.background
color: Commons.Color.popups.background
borderSpec: root.borderSpec
padding: root.padding
radius: Style.cornerRadius
+6 -5
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls as QQC
import QtQuick.Window
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
// Searchable multi-select dropdown. Trigger shape matches Dropdown /
// SearchableDropdown; the popup shows a search field, an optional refresh
@@ -33,11 +34,11 @@ Item {
property string triggerLabel: ""
property bool showLabel: true
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+3 -2
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls as QQC
import qs.Commons
import qs.Commons as Commons
Column {
id: root
@@ -10,8 +11,8 @@ Column {
property int from: 0
property int to: 100
property int stepSize: 1
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property real fieldWidth: Style.spacing.numberFieldWidth
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -7,7 +8,7 @@ Item {
property string text: ""
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property color color: Color.foreground
property color color: Commons.Color.foreground
property bool debugBounds: false
readonly property int renderedFontSize: Math.max(1, Math.round(fontSize))
+57 -24
View File
@@ -3,12 +3,9 @@ import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
// A fullscreen overlay whose surface outlives each open. A fresh surface draws
// its first frames before the compositor sends its fractional scale, so an
// overlay mapped per open flashed blurry until the scale arrived. Closed, the
// surface parks as a 1x1, input-less, content-less layer below windows: small
// enough to cost nothing, off the overlay layer so it never blocks direct
// scanout for fullscreen apps. Opening only resizes and raises it.
// Map a fresh fullscreen surface on each open. Keep its first frames
// transparent until Qt receives the output's scale, so it opens sharp without
// retaining a parked surface that can be closed when a monitor disconnects.
PanelWindow {
id: window
@@ -17,11 +14,31 @@ PanelWindow {
property int shownLayer: WlrLayer.Overlay
property int shownKeyboardFocus: WlrKeyboardFocus.Exclusive
// The surface no longer lands on the focused output by being mapped there,
// so it follows the focused monitor each time it is shown. Unset until the
// first show lets the compositor choose.
property var targetScreen: null
property Region emptyRegion: Region {}
readonly property var targetMonitor: {
var monitors = Hyprland.monitors.values
for (var i = 0; i < monitors.length; i++) {
if (targetScreen && monitors[i].name === targetScreen.name) return monitors[i]
}
return null
}
// Wait for fullscreen geometry as well as scale so no smaller opening frame
// is stretched across the output. Fractional scale uses units of 1/120;
// compare in those units to tolerate floating point rounding.
readonly property bool contentReady: shown && backingWindowVisible && !!targetScreen
&& width === targetScreen.width && height === targetScreen.height
&& !!targetMonitor && Math.round(devicePixelRatio * 120) === Math.round(targetMonitor.scale * 120)
property bool contentRevealed: false
onContentReadyChanged: {
contentRevealed = false
// Resize and scale notifications arrive while Qt is still updating the
// window. Reveal on the next turn, once its content layout has caught up.
if (contentReady) Qt.callLater(function() {
if (window.contentReady) window.contentRevealed = true
})
}
function focusedScreen() {
var monitor = Hyprland.focusedMonitor
@@ -29,28 +46,44 @@ PanelWindow {
for (var i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === name) return Quickshell.screens[i]
}
return null
return Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
}
onShownChanged: if (shown) targetScreen = focusedScreen() || targetScreen
// Live monitor scaling does not emit a Hyprland monitor event, so its cached
// scale can lag behind Qt. Refresh on opening and on scale changes while open.
onShownChanged: {
targetScreen = shown ? focusedScreen() : null
if (shown) Hyprland.refreshMonitors()
}
onDevicePixelRatioChanged: if (shown) Hyprland.refreshMonitors()
visible: true
Connections {
target: Quickshell
function onScreensChanged() {
if (window.shown && Quickshell.screens.indexOf(window.targetScreen) < 0) {
window.targetScreen = null
// Let the removed output finish closing its old window before mapping
// a replacement, rather than recreating it during Qt's teardown.
Qt.callLater(function() {
if (window.shown && !window.targetScreen) window.targetScreen = window.focusedScreen()
})
}
}
}
visible: shown && !!targetScreen && !!targetMonitor
screen: targetScreen
anchors { top: true; left: true; bottom: shown; right: shown }
implicitWidth: 1
implicitHeight: 1
mask: shown ? null : emptyRegion
anchors { top: true; left: true; bottom: true; right: true }
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: shown ? shownLayer : WlrLayer.Bottom
WlrLayershell.keyboardFocus: shown ? shownKeyboardFocus : WlrKeyboardFocus.None
WlrLayershell.layer: shownLayer
WlrLayershell.keyboardFocus: shownKeyboardFocus
// Draw nothing until the surface has actually grown. A frame drawn while it
// is still 1x1 holds only the scrim's color, which the compositor would
// stretch across the whole screen until the fullscreen frame arrives.
// Opacity preserves keyboard handling while the scale arrives, including
// search keystrokes and Escape pressed immediately after opening.
Binding {
target: window.contentItem
property: "visible"
value: window.shown && window.width > 1 && window.height > 1
property: "opacity"
value: window.contentRevealed ? 1 : 0
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
// Base item for plugin popup widgets. Many first-party plugins expose a bar
// button plus a popup from one QML entry point; this base owns the shared
@@ -19,7 +20,7 @@ Item {
property bool popoutSwitchClosing: false
readonly property bool opened: panelController.open
readonly property color barForeground: bar ? bar.barForeground : Color.foreground
readonly property color barForeground: bar ? bar.barForeground : Commons.Color.foreground
function open() { panelController.show() }
function close() { panelController.hide() }
+9 -4
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Small (22×22 by default) icon button used at the right edge of panel rows
// for inline actions — forget network, confirm passphrase, unpair device,
@@ -29,11 +30,13 @@ BorderSurface {
property string iconText: ""
property string tooltipText: ""
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color hoverColor: foreground
property string fontFamily: Style.font.family
property real fontSize: Style.font.icon
property real size: Math.max(Style.space(22), fontSize + Style.spacing.sm * 2)
// Whole and odd, so an icon glyph (usually an odd number of pixels wide)
// has the same space on both sides instead of one pixel more on one side.
property real size: Math.round(Math.max(Style.space(22), fontSize + Style.spacing.sm * 2)) | 1
property bool focusable: false
property bool hasCursor: false
@@ -57,7 +60,7 @@ BorderSurface {
? Border.controlSpec("focus", hoverColor, hoverColor)
: (_hot && bordered
? Border.controlSpec("hover-cursor", hoverColor, hoverColor)
: (bordered ? Border.controlSpec("normal", foreground, Color.accent) : Border.none()))
: (bordered ? Border.controlSpec("normal", foreground, Commons.Color.accent) : Border.none()))
color: _showFocusRing
? Style.focusFillFor(hoverColor, hoverColor)
@@ -70,7 +73,9 @@ BorderSurface {
Text {
textFormat: Text.PlainText
anchors.centerIn: parent
// Snap to whole pixels so the glyph doesn't round away from the fill's center.
x: Math.round((root.width - width) / 2)
y: Math.round((root.height - height) / 2)
text: root.iconText
color: root.enabled
? (root._hot ? root.hoverColor : root.foreground)
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -8,7 +9,7 @@ Item {
property string title: ""
property string meta: ""
property string detail: ""
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property string fontFamily: Style.font.family
property real iconSize: Style.font.display
property real iconOpacity: 1.0
@@ -71,7 +72,7 @@ Item {
implicitHeight: detailText.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.foreground, Commons.Color.accent)
radius: Style.cornerRadius
Text {
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Small-caps-style label that introduces a panel section ("DNS provider",
// "Wi-Fi networks", "Output device", "Paired devices"). Sits between a
@@ -7,7 +8,7 @@ import qs.Commons
Text {
id: root
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property string fontFamily: Style.font.family
property real fontSize: Style.font.caption
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// 1px horizontal divider for panel sections. The alpha-on-foreground tint
// keeps the rule legible against the panel background without competing
@@ -7,7 +8,7 @@ import qs.Commons
Rectangle {
id: root
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property real strength: 0.12
width: parent ? parent.width : implicitWidth
+5 -4
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -10,9 +11,9 @@ Item {
property real maximum: 1
property real step: 0.05
property bool integer: false
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "#333"
property color fillColor: bar ? bar.foreground : Color.foreground
property color knobColor: bar ? bar.foreground : Color.foreground
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Commons.Color.accent) : "#333"
property color fillColor: bar ? bar.foreground : Commons.Color.foreground
property color knobColor: bar ? bar.foreground : Commons.Color.foreground
property bool dragging: false
property real trackHeight: Math.max(4, Math.round(Style.spacing.controlHeight * 0.11))
property real knobSize: Math.max(14, Math.round(Style.spacing.controlHeight * 0.38))
@@ -23,7 +24,7 @@ Item {
// crossing the track shows). Purely visual — snapping is the caller's job via
// `integer`/`step` or an index-based value. Default 0 leaves the track plain.
property int tickCount: 0
property color tickColor: bar ? bar.background : Color.background
property color tickColor: bar ? bar.background : Commons.Color.background
onValueChanged: if (!dragging) liveValue = value
+6 -5
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Styled wrapper around Qt Quick Controls ToolTip. Drop-in: declare inside
// the hovered item and bind `visible` to the hover state, e.g.
@@ -9,7 +10,7 @@ import qs.Commons
// text: "Forget network"
// }
//
// Defaults pull from [tooltip] in shell.toml via Color.tooltip.*. Override
// Defaults pull from [tooltip] in shell.toml via Commons.Color.tooltip.*. Override
// the panel* properties per-instance only when you need a tooltip that
// intentionally diverges from the theme.
//
@@ -18,13 +19,13 @@ import qs.Commons
ToolTip {
id: root
property color panelForeground: Color.tooltip.text
property color panelBackground: Color.tooltip.background
property color panelBorder: Color.tooltip.border
property color panelForeground: Commons.Color.tooltip.text
property color panelBackground: Commons.Color.tooltip.background
property color panelBorder: Commons.Color.tooltip.border
property string fontFamily: Style.font.family
property real fontSize: Style.font.bodySmall
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Color.tooltip.border, Style.normalBorderWidth)
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Commons.Color.tooltip.border, Style.normalBorderWidth)
delay: 400
padding: 0
+4 -3
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.Commons
import qs.Commons as Commons
PopupWindow {
id: root
@@ -13,8 +14,8 @@ PopupWindow {
property int padding: Style.spacing.popupPadding
property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200)
property color borderColor: Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Color.popups.border, Math.max(1, Style.space(2)))
property color borderColor: Commons.Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Commons.Color.popups.border, Math.max(1, Style.space(2)))
property bool open: false
property bool centerOnBar: false
// "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup.
@@ -150,7 +151,7 @@ PopupWindow {
BorderSurface {
id: card
anchors.fill: parent
color: Color.popups.background
color: Commons.Color.popups.background
borderSpec: root.borderSpec
padding: root.padding
radius: Style.cornerRadius
+6 -5
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls as QQC
import qs.Commons
import qs.Commons as Commons
// Searchable single-select dropdown. Same trigger shape as Dropdown, but
// the popup leads with an embedded TextField that filters the option
@@ -25,11 +26,11 @@ Item {
property string emptyText: "No matches"
property string triggerLabel: ""
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+5 -4
View File
@@ -4,6 +4,7 @@ import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
// Centered speed test overlay shared by the network and disk speed tests. No
@@ -288,7 +289,7 @@ PanelWindow {
// round caps would leave a stray dot at the foot of the scale.
ShapePath {
strokeWidth: dial.arcWidth * 3
strokeColor: dial.arcVisible ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.18) : "transparent"
strokeColor: dial.arcVisible ? Qt.rgba(Commons.Color.accent.r, Commons.Color.accent.g, Commons.Color.accent.b, 0.18) : "transparent"
fillColor: "transparent"
capStyle: ShapePath.RoundCap
@@ -305,7 +306,7 @@ PanelWindow {
// Value: fills behind the needle.
ShapePath {
strokeWidth: dial.arcWidth
strokeColor: dial.arcVisible ? Color.accent : "transparent"
strokeColor: dial.arcVisible ? Commons.Color.accent : "transparent"
fillColor: "transparent"
capStyle: ShapePath.RoundCap
@@ -356,8 +357,8 @@ PanelWindow {
radius: width / 2
gradient: Gradient {
GradientStop { position: 0.0; color: Color.accent }
GradientStop { position: 0.55; color: Color.accent }
GradientStop { position: 0.0; color: Commons.Color.accent }
GradientStop { position: 0.55; color: Commons.Color.accent }
GradientStop { position: 1.0; color: "transparent" }
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Single-line text input with the kit's focus + selection styling. Inherits
// from Qt Quick Controls TextField so the underlying type's API (text,
@@ -18,8 +19,8 @@ import qs.Commons
TextField {
id: root
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property color selectionTint: Style.selectionFillFor(foreground, accent)
property bool password: false
property real horizontalPadding: Style.spacing.controlPaddingX
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Labeled toggle row: title + optional description on the left, a
// `ToggleSwitch` on the right. Clicking anywhere on the row emits `clicked()`;
@@ -28,8 +29,8 @@ BorderSurface {
// Override per-instance if a caller wants the opposite.
property bool rounded: Style.cornerRadius > 0
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real titleSize: Style.font.subtitle
property real descriptionSize: Style.font.caption
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Bare on/off switch: a track with a sliding knob and no label. This is the
// switch `Toggle` parks at the end of its labeled row, factored out so panel
@@ -40,8 +41,8 @@ Item {
property bool cursorRing: interactive
property int cursorPad: Style.space(6)
property bool rounded: Style.cornerRadius > 0
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
signal toggled()
signal hovered(bool isHovered)
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -8,8 +9,8 @@ Item {
property string text: ""
property string fontFamily: bar ? bar.fontFamily : Style.font.family
property real fontSize: Style.font.body
property color foreground: bar ? bar.barForeground : Color.foreground
property color activeColor: bar ? bar.urgent : Color.urgent
property color foreground: bar ? bar.barForeground : Commons.Color.foreground
property color activeColor: bar ? bar.urgent : Commons.Color.urgent
property bool active: false
property real horizontalMargin: 8.5
property real verticalPadding: 6
+17 -16
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Panel {
@@ -11,11 +12,11 @@ Panel {
ipcTarget: "omarchy.agents"
manageIpc: false
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color urgent: bar ? bar.urgent : Color.urgent
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property color urgent: bar ? bar.urgent : Commons.Color.urgent
readonly property color dim: Qt.darker(foreground, 1.55)
readonly property color surface: Color.popups.background
readonly property color track: Style.selectedFillFor(foreground, Color.accent)
readonly property color surface: Commons.Color.popups.background
readonly property color track: Style.selectedFillFor(foreground, Commons.Color.accent)
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
// Every subscription on one page, limits first: the question this panel
@@ -719,7 +720,7 @@ Panel {
function iconCandidatesForProvider(p, surfaceColor) {
if (!p) return []
var candidates = []
if (colorLuminance(surfaceColor || Color.background) >= 0.5)
if (colorLuminance(surfaceColor || Commons.Color.background) >= 0.5)
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + "-light.svg"))
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + ".svg"))
return candidates
@@ -1109,7 +1110,7 @@ Panel {
anchors.horizontalCenter: parent.horizontalCenter
textFormat: Text.PlainText
text: choice.modelData.providerName
color: choice.hasCursor ? Color.accent : root.foreground
color: choice.hasCursor ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
@@ -1187,7 +1188,7 @@ Panel {
Text {
textFormat: Text.PlainText
text: root.addCode
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.display
font.bold: true
@@ -1528,7 +1529,7 @@ Panel {
property bool hasCursor: false
implicitHeight: tileBody.implicitHeight + Style.space(20)
radius: Style.cornerRadius
color: hasCursor ? root.alpha(Color.accent, 0.14) : root.alpha(root.foreground, 0.05)
color: hasCursor ? root.alpha(Commons.Color.accent, 0.14) : root.alpha(root.foreground, 0.05)
onHasCursorChanged: if (hasCursor) root.revealItem(tile)
Row {
@@ -1540,7 +1541,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
textFormat: Text.PlainText
text: tile.glyph
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.heading
}
@@ -1549,7 +1550,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
textFormat: Text.PlainText
text: tile.title
color: tile.hasCursor ? Color.accent : root.foreground
color: tile.hasCursor ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
@@ -1583,16 +1584,16 @@ Panel {
implicitHeight: implicitWidth
radius: Style.cornerRadius
// The cursor needs more than a shade deeper to read on a tinted square.
color: root.alpha(Color.accent, hasCursor ? 0.3 : 0.12)
color: root.alpha(Commons.Color.accent, hasCursor ? 0.3 : 0.12)
border.width: hasCursor ? Math.max(1, Style.hoverBorderWidth) : 0
border.color: Color.accent
border.color: Commons.Color.accent
onHasCursorChanged: if (hasCursor) root.revealItem(heroButton)
Text {
anchors.centerIn: parent
textFormat: Text.PlainText
text: heroButton.glyph
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.heading
}
@@ -1624,7 +1625,7 @@ Panel {
readonly property bool hot: linkMouse.containsMouse || picked
onPickedChanged: if (picked) root.revealItem(link)
textFormat: Text.PlainText
color: current ? Color.accent : (hot ? root.foreground : idleColor)
color: current ? Commons.Color.accent : (hot ? root.foreground : idleColor)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: current
@@ -1705,7 +1706,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, parent.width * 0.6)
text: head.label
color: head.picked ? Color.accent : root.foreground
color: head.picked ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: head.isActive
@@ -1810,7 +1811,7 @@ Panel {
visible: head.isActive
anchors.right: parent.right
text: "ACTIVE"
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
+34 -7
View File
@@ -5,12 +5,14 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Item {
id: root
property var shell: null
property bool suspended: false
readonly property string home: Quickshell.env("HOME")
readonly property string stateHome: home + "/.local/state"
readonly property string currentBackgroundLink: stateHome + "/omarchy/current/background"
@@ -37,11 +39,20 @@ Item {
property var sizeQueue: []
property bool finishingTransition: false
property int backgroundVersion: 0
property int reloadVersion: 0
property int revealStartedVersion: -1
property int pendingThemeVersion: -1
property string pendingColorsRaw: ""
property string pendingShellRaw: ""
property real revealProgress: 1
readonly property bool ready: {
if (isVideo(displayedBackground)) return true
if (backgrounds.instances.length === 0) return false
for (var panel of backgrounds.instances) {
if (!panel.backgroundReady) return false
}
return true
}
function isVideo(path) {
return Util.isVideoPath(path)
@@ -56,7 +67,8 @@ Item {
}
function setBackground(path, instant) {
transitionBackground("", path, path, instant, false)
if (instant) reloadVersion += 1
transitionBackground("", path, path, instant, instant)
}
function transitionBackground(fromPath, path, finalPath, instant, force) {
@@ -106,10 +118,10 @@ Item {
// pending; the latest theme payload should still apply.
if (pendingThemeVersion < 0) return
pendingThemeFallbackTimer.stop()
Color.loadColors(pendingColorsRaw)
// Color.loadShell also refreshes Style so the type scale flips with the
Commons.Color.loadColors(pendingColorsRaw)
// Commons.Color.loadShell also refreshes Style so the type scale flips with the
// background reveal instead of waiting for a separate reload path.
Color.loadShell(pendingShellRaw)
Commons.Color.loadShell(pendingShellRaw)
Style.scheduleRefresh()
pendingThemeVersion = -1
pendingColorsRaw = ""
@@ -223,6 +235,10 @@ Item {
root.setBackground(path, true)
}
function setSuspended(value: string): void {
root.suspended = value === "true"
}
function transition(fromPath: string, path: string): void {
root.transitionBackground(fromPath, path, path, false, false)
}
@@ -258,8 +274,8 @@ Item {
property: "revealProgress"
from: 0
to: 1
duration: Style.duration(420)
easing.type: Easing.InOutCubic
duration: Style.duration(840)
easing.type: Easing.OutCubic
onFinished: {
if (root.incomingBackground) {
root.displayedBackground = root.currentBackground || root.incomingBackground
@@ -272,6 +288,7 @@ Item {
Component.onCompleted: refreshBackground()
Variants {
id: backgrounds
model: Quickshell.screens
PanelWindow {
@@ -279,7 +296,7 @@ Item {
required property var modelData
screen: modelData
visible: !remapGuard.remapping
visible: !remapGuard.remapping && !root.suspended
anchors { top: true; bottom: true; left: true; right: true }
ScreenMoveRemap {
@@ -294,6 +311,13 @@ Item {
updatesEnabled: true
property bool maskReady: false
property int readyFrames: 0
readonly property bool backgroundReady: base.ready && readyFrames >= 2
FrameAnimation {
running: base.ready && panel.readyFrames < 2
onTriggered: panel.readyFrames += 1
}
// Decode the wallpaper at the size this screen can show, not the size
// it was shipped at. With PreserveAspectCrop Qt takes sourceSize as the
@@ -337,9 +361,12 @@ Item {
id: base
anchors.fill: parent
path: root.displayedBackground
version: root.reloadVersion
cached: true
constrainDecode: true
decodeSize: panel.decodeSize(root.displayedBackground)
onReadyChanged: {
panel.readyFrames = 0
if (ready && root.finishingTransition) {
root.incomingBackground = ""
root.oldBackground = ""
+14 -13
View File
@@ -5,6 +5,7 @@ import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "BarModel.js" as BarModel
@@ -71,18 +72,18 @@ Item {
property string fontFamily: Style.font.family
// Bound to the central Color singleton so the bar tracks shell.toml's
// [bar] section. Property names kept for the rest of this file's bindings.
property color themeForeground: Color.bar.text
property color themeContrastForeground: Color.background
property color transparentForeground: Color.bar.text
property color themeForeground: Commons.Color.bar.text
property color themeContrastForeground: Commons.Color.background
property color transparentForeground: Commons.Color.bar.text
property color foreground: themeForeground
property color barForeground: useTransparentForeground ? transparentForeground : themeForeground
property bool foregroundAnimationEnabled: true
property color background: Color.bar.background
property color urgent: Color.bar.active
property color background: Commons.Color.bar.background
property color urgent: Commons.Color.bar.active
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on background { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on urgent { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
Behavior on background { ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
Behavior on urgent { ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
property var tooltipTarget: null
property var pendingTooltipTarget: null
property string tooltipText: ""
@@ -1342,8 +1343,8 @@ Item {
id: tooltipBubble
implicitWidth: tooltipLabel.implicitWidth + 20
implicitHeight: tooltipLabel.implicitHeight + 14
color: Color.tooltip.background
borderSpec: Border.surfaceSpec("tooltip", "border", Color.tooltip.border, 1)
color: Commons.Color.tooltip.background
borderSpec: Border.surfaceSpec("tooltip", "border", Commons.Color.tooltip.border, 1)
radius: Style.cornerRadius
Text {
@@ -1351,7 +1352,7 @@ Item {
textFormat: Text.PlainText
anchors.centerIn: parent
text: root.tooltipText
color: Color.tooltip.text
color: Commons.Color.tooltip.text
font.family: root.fontFamily
font.pixelSize: Style.font.body
horizontalAlignment: Text.AlignHCenter
@@ -1470,7 +1471,7 @@ Item {
y: targetRect ? Math.round(targetRect.y) : 0
width: targetRect ? targetRect.width : 0
height: targetRect ? targetRect.height : 0
color: Color.accent
color: Commons.Color.accent
radius: Math.min(width, height) / 2
}
}
@@ -1905,7 +1906,7 @@ Item {
visible: opacity > 0
opacity: slot.panelOpen && !slot.dragSource ? 0.9 : 0
color: Color.accent
color: Commons.Color.accent
radius: Math.min(width, height) / 2
width: root.vertical ? Style.space(2) : slot.panelIndicatorExtent
height: root.vertical ? slot.panelIndicatorExtent : Style.space(2)
@@ -1,38 +0,0 @@
import QtQuick
import Quickshell.Io
import qs.Ui
BarIndicator {
id: root
property string state: "idle"
property string icon: ""
active: state === "recording"
activeText: icon
inactiveText: "󰍬"
activeTooltipText: state
inactiveTooltipText: "Dictate"
function update(raw) {
var data = extractData(raw)
state = String(data.alt || data.class || "idle")
if (state === "recording") icon = "󰍬"
else if (state === "transcribing") icon = "󰔟"
else icon = ""
}
Process {
command: ["bash", "-c", "omarchy-voxtype-status"]
running: true
stdout: SplitParser {
onRead: function(data) { root.update(data) }
}
}
onPressed: function() {
if (!root.bar) return
root.bar.run("omarchy-voxtype-config")
}
}
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
BarIndicator {
@@ -15,7 +16,7 @@ BarIndicator {
activeTooltipText: "Disable Passwordless Sudo"
inactiveTooltipText: "Passwordless Sudo"
useActiveColor: true
activeColor: Color.urgent
activeColor: Commons.Color.urgent
function refresh() {
if (!root.bar) return
+2 -1
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
BarWidget {
@@ -34,7 +35,7 @@ BarWidget {
anchors.left: parent.left
width: parent.width
text: root.title
color: root.bar ? root.bar.barForeground : Color.foreground
color: root.bar ? root.bar.barForeground : Commons.Color.foreground
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.body
elide: Text.ElideRight
@@ -31,11 +31,6 @@
"label": "Passwordless sudo",
"description": "Warning while passwordless root access is enabled"
},
{
"value": "Dictation",
"label": "Dictation",
"description": "Voice typing status"
},
{
"value": "ScreenRecording",
"label": "Screen recording",
+1 -1
View File
@@ -8,7 +8,7 @@ BarWidget {
id: root
moduleName: "omarchy.indicators"
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
property var activeIndicatorIds: []
property var indicatorActiveStates: ({})
+4 -3
View File
@@ -4,6 +4,7 @@ import QtQuick.Controls
import QtQuick.Effects
import Quickshell.Services.SystemTray
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "TrayModel.js" as TrayModel
@@ -16,7 +17,7 @@ BarWidget {
property bool trayMenuOpen: false
property var activeTrayItem: null
property var activeTrayAnchor: null
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property var pinnedIds: settings.pinned instanceof Array ? settings.pinned : []
readonly property var hiddenIds: settings.hidden instanceof Array ? settings.hidden : []
@@ -617,7 +618,7 @@ BarWidget {
anchors.rightMargin: Style.space(10)
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Color.popups.border
color: Commons.Color.popups.border
opacity: 0.45
}
}
@@ -671,7 +672,7 @@ BarWidget {
anchors.rightMargin: Style.space(10)
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Color.popups.border
color: Commons.Color.popups.border
opacity: 0.45
}
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "ClipboardHistory.js" as ClipboardHistory
@@ -22,13 +23,13 @@ Item {
// Shares the [menu] surface tokens — themes that style the menu also
// style the clipboard. Selected-row colors composed in the
// singleton so consumers drop them straight into Rectangle bindings.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
readonly property int cornerRadius: Style.cornerRadius
property string fontFamily: Style.font.menuFamily
property int contentMargin: Style.spacing.panelPadding
+5 -4
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls
import Quickshell
import qs.Ui
import qs.Commons
import qs.Commons as Commons
// Visual reference + live playground for omarchy-shell's common UI
// components. Summon with `omarchy dev ui-preview`, or directly via:
@@ -66,10 +67,10 @@ Item {
property var shell: null
// ---- theme --------------------------------------------------------------
readonly property color foreground: Color.foreground
readonly property color background: Color.background
readonly property color accent: Color.accent
readonly property color urgent: Color.urgent
readonly property color foreground: Commons.Color.foreground
readonly property color background: Commons.Color.background
readonly property color accent: Commons.Color.accent
readonly property color urgent: Commons.Color.urgent
readonly property string fontFamily: "monospace"
// Fake `bar` for components that take a whole bar object (e.g. Slider).
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "EmojiSearch.js" as EmojiSearch
@@ -23,13 +24,13 @@ Item {
// Shares the [menu] surface tokens — themes that style the menu also
// style emojis. Selected-cell colors composed in the
// singleton so consumers drop them straight into Rectangle bindings.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
readonly property int cornerRadius: Style.cornerRadius
property string fontFamily: Style.font.menuFamily
property int contentMargin: Style.spacing.panelPadding
+235 -33
View File
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "ImagePickerModel.js" as ImagePickerModel
@@ -14,6 +15,7 @@ Item {
// Injected by omarchy-shell; defaults to the session OMARCHY_PATH.
property string omarchyPath: Quickshell.env("OMARCHY_PATH")
property string stateHome: Quickshell.env("HOME") + "/.local/state"
property string userThemesPath: Quickshell.env("HOME") + "/.config/omarchy/themes"
property string imageDirs: Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIRS") || Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIR") || Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (stateHome + "/omarchy/current/theme/backgrounds")
property string imageRows: ""
property string loadedImageRows: ""
@@ -26,6 +28,7 @@ Item {
property bool filterable: false
property bool layoutSettled: false
property bool neighborImagesEnabled: false
property int renderedFrames: 0
property bool requestActive: false
property int requestSerial: 0
property int applySerial: 0
@@ -37,14 +40,21 @@ Item {
property string themeRows: ""
property bool themeMode: false
property bool themeOpenPending: false
// Bound to the central [image-picker] section in shell.toml via Color.qml.
// `dimColor` tints unselected slices and text outlines on top of the scrim;
// it intentionally tracks the foundational background, not a surface role.
property color dimColor: Color.background
property color foreground: Color.imagePicker.text
property color scrim: Color.imagePicker.scrim
property color selectedBorder: Color.imagePicker.selectedBorder
property color unselectedBorder: Color.imagePicker.unselectedBorder
property var extraThemeNames: []
property var stockThemeNames: []
property bool stockThemesKnown: false
property bool awaitingDeleteRefresh: false
property string pendingDeleteTheme: ""
property string deleteSelectionPath: ""
property bool deleteConfirmOpen: false
// Bound to the central [image-picker] section in shell.toml via Commons.Color.qml.
// `dimColor` tints unselected slices and text outlines against the desktop
// behind the picker; it intentionally tracks the foundational background,
// not a surface role.
property color dimColor: Commons.Color.background
property color foreground: Commons.Color.imagePicker.text
property color selectedBorder: Commons.Color.imagePicker.selectedBorder
property color unselectedBorder: Commons.Color.imagePicker.unselectedBorder
property int expandedWidth: 768
property int expandedHeight: 475
property int sliceWidth: 108
@@ -56,7 +66,7 @@ Item {
readonly property int previewRadius: Math.max(1, Math.min(16, Math.ceil((panel.width - expandedWidth) / (2 * (sliceWidth + sliceSpacing))) + 1))
onPreviewRadiusChanged: updateVisibleItems()
onOpenedChanged: if (!opened) layoutSettled = false
onOpenedChanged: if (!opened) { layoutSettled = false; renderedFrames = 0; deleteConfirmOpen = false; pendingDeleteTheme = ""; deleteSelectionPath = ""; awaitingDeleteRefresh = false }
function scriptPath(name) {
return omarchyPath + "/shell/plugins/image-picker/" + name
@@ -69,10 +79,35 @@ Item {
function revealWhenSettled(serial) {
Qt.callLater(function() {
if (serial === root.requestSerial && root.opened && root.imagesLoaded && root.imageArray.length > 0) {
root.layoutSettled = true
root.focusPicker()
}
if (serial === root.requestSerial) root.maybeReveal()
})
}
// The card renders at opacity 0 until every visible preview has decoded
// and its masked layers have presented, so the picker lands in one
// complete frame instead of flashing its images in as each asynchronous
// decode and layer upload finishes.
function allPreviewsSettled() {
if (!opened || !imagesLoaded || layoutSettled || imageArray.length === 0 || imageCards.count === 0) return false
for (var i = 0; i < imageCards.count; i++) {
var item = imageCards.itemAt(i)
if (!item || !item.previewSettled) return false
}
return true
}
function settleReveal() {
if (!opened || !imagesLoaded || layoutSettled || imageArray.length === 0) return
layoutSettled = true
focusPicker()
}
function maybeReveal() {
if (!allPreviewsSettled() || renderedFrames < 2) return
// Confirm on the next tick: delegates inserted later in the same window
// sync must not pop in after an early all-ready reading.
Qt.callLater(function() {
if (root.allPreviewsSettled() && root.renderedFrames >= 2) root.settleReveal()
})
}
@@ -152,6 +187,7 @@ Item {
}
function applySelected() {
if (themeMode && (deleteThemeProc.running || awaitingDeleteRefresh)) return
var path = currentPath()
if (themeMode) {
@@ -224,6 +260,10 @@ Item {
}
function openSelector(nextImageDirs, nextImageRows, nextSelectedImage, nextSelectionFile, nextDoneFile, nextShowLabels, nextFilterable) {
deleteConfirmOpen = false
pendingDeleteTheme = ""
deleteSelectionPath = ""
awaitingDeleteRefresh = false
if (requestActive && doneFile && doneFile !== nextDoneFile)
finishDoneFile(doneFile)
@@ -274,7 +314,29 @@ Item {
property var imageArray: []
readonly property var matchingImageIndices: ImagePickerModel.matchingIndices(imageArray, filterText)
onMatchingImageIndicesChanged: updateVisibleItems()
onSelectedIndexChanged: updateVisibleItems()
onSelectedIndexChanged: {
updateVisibleItems()
introPrepareTimer.restart()
}
onThemeModeChanged: if (themeMode) introPrepareTimer.restart()
Timer {
id: introPrepareTimer
interval: 75
onTriggered: {
if (root.opened && root.themeMode && !introPrepare.running) {
introPrepare.theme = root.nameForPath(root.currentPath())
introPrepare.command = ["omarchy-theme-bg-boot-intro", "--prepare-theme", root.nameForPath(root.currentPath())]
introPrepare.running = true
}
}
}
Process {
id: introPrepare
property string theme: ""
onExited: if (root.opened && root.themeMode && theme !== root.nameForPath(root.currentPath())) introPrepareTimer.restart()
}
function updateVisibleItems() {
ImagePickerModel.syncWindow(visibleImages, ImagePickerModel.visibleWindow(matchingImageIndices, selectedIndex, previewRadius))
@@ -307,6 +369,8 @@ Item {
themeOpenPending = true
}
refreshThemeRows()
refreshExtraThemes()
refreshStockThemes()
}
function openThemeRows() {
@@ -314,8 +378,53 @@ Item {
themeMode = true
}
// A refresh asked mid-refresh (a deletion finishing behind the open-time
// one) queues a second pass instead of leaving the list stale.
function refreshThemeRows() {
if (!themeRowsProc.running) themeRowsProc.running = true
if (themeRowsProc.running) { themeRowsProc.queued = true; return }
themeRowsProc.running = true
}
function refreshExtraThemes() {
if (extraThemesProc.running) { extraThemesProc.queued = true; return }
extraThemesProc.running = true
}
function refreshStockThemes() {
if (!stockThemesProc.running) stockThemesProc.running = true
}
function selectedThemeName() {
return nameForPath(currentPath())
}
function canDeleteSelectedTheme() {
return themeMode && !deleteThemeProc.running && !awaitingDeleteRefresh && stockThemesKnown && ImagePickerModel.canDeleteTheme(selectedThemeName(), extraThemeNames, stockThemeNames)
}
function requestDeleteSelectedTheme() {
if (!canDeleteSelectedTheme()) return
pendingDeleteTheme = selectedThemeName()
deleteConfirm.selectedIndex = 1
deleteConfirmOpen = true
}
function cancelDeleteTheme() {
deleteConfirmOpen = false
pendingDeleteTheme = ""
}
// Removal itself is omarchy-theme-remove's job (including its guards and
// notification); the picker only refreshes its rows once it finishes.
function confirmDeleteTheme() {
var name = pendingDeleteTheme
deleteConfirmOpen = false
pendingDeleteTheme = ""
if (!name || deleteThemeProc.running) return
// Keep the user's place in the list once the deleted theme is gone.
if (name === selectedThemeName()) deleteSelectionPath = ImagePickerModel.replacementSelectionPath(imageArray, selectedIndex, filterText)
deleteThemeProc.command = ["omarchy-theme-remove", name]
deleteThemeProc.running = true
}
function updateThemeRows(rows) {
@@ -327,8 +436,11 @@ Item {
if (rows) openThemeRows()
} else if (changed && rows && themeMode && opened) {
// A theme was added or removed since the rows were last read. Keep the
// user's place in the carousel rather than jumping back to the current.
selectedImage = currentPath() || currentThemePreview()
// user's place in the carousel rather than jumping back to the current;
// a just-deleted selection lands on the theme before it.
selectedImage = deleteSelectionPath || currentPath() || currentThemePreview()
deleteSelectionPath = ""
awaitingDeleteRefresh = false
imageRows = rows
loadRows(rows, false)
}
@@ -343,13 +455,62 @@ Item {
Process {
id: themeRowsProc
property bool queued: false
command: [root.omarchyPath + "/bin/omarchy-theme-switcher", "--print-rows"]
stdout: StdioCollector {
onStreamFinished: root.updateThemeRows(String(text || "").trim())
}
onExited: {
if (queued) {
queued = false
running = true
}
}
}
Component.onCompleted: refreshThemeRows()
// The same listing omarchy-theme-remove offers interactively: real
// directories under the user themes path, never symlinked working copies.
Process {
id: extraThemesProc
property bool queued: false
command: ["find", root.userThemesPath, "-mindepth", "1", "-maxdepth", "1", "-type", "d", "!", "-xtype", "l", "-printf", "%f\n"]
stdout: StdioCollector {
onStreamFinished: root.extraThemeNames = ImagePickerModel.parseThemeNames(String(text || ""))
}
onExited: {
if (queued) {
queued = false
running = true
}
}
}
Process {
id: stockThemesProc
command: ["find", root.omarchyPath + "/themes", "-mindepth", "1", "-maxdepth", "1", "-type", "d", "-printf", "%f\n"]
stdout: StdioCollector {
onStreamFinished: root.stockThemeNames = ImagePickerModel.parseThemeNames(String(text || ""))
}
// Until this lands, stockThemeNames is empty and an override would look
// deletable: deletion stays disabled unless the listing succeeded.
onExited: function(exitCode) { if (exitCode === 0) root.stockThemesKnown = true }
}
Process {
id: deleteThemeProc
onExited: function(exitCode) {
if (exitCode === 0) root.awaitingDeleteRefresh = true
else root.deleteSelectionPath = ""
root.refreshExtraThemes()
root.refreshThemeRows()
}
}
Component.onCompleted: {
refreshThemeRows()
refreshExtraThemes()
refreshStockThemes()
}
function startImageScan(serial, dirs) {
if (loadImagesProc.running) {
@@ -427,7 +588,7 @@ Item {
// Theme/background set hooks can warm selector rows after a picker was
// dismissed. Ignore those preloads while a user-visible request is open;
// otherwise the preload resets layoutSettled without revealing again,
// leaving only the fullscreen scrim.
// leaving the picker invisible behind its own open overlay.
if (opened || requestActive) return
requestSerial += 1
@@ -465,28 +626,39 @@ Item {
shownKeyboardFocus: root.imagesLoaded ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.namespace: "omarchy-image-selector"
Rectangle {
anchors.fill: parent
visible: root.opened && root.imagesLoaded
color: root.scrim
}
MouseArea {
anchors.fill: parent
enabled: root.opened && root.imagesLoaded
onClicked: root.cancel()
}
// Count presented frames while the card pre-renders below; two frames
// prove its masked layers uploaded on this fresh surface.
FrameAnimation {
running: root.opened && !root.layoutSettled && root.renderedFrames < 2
onTriggered: {
root.renderedFrames += 1
root.maybeReveal()
}
}
// A preview stuck decoding (slow disk, huge original) must not hold an
// invisible picker with the keyboard grabbed: reveal anyway shortly.
Timer {
interval: 400
running: root.opened && root.imagesLoaded && !root.layoutSettled && root.imageArray.length > 0
onTriggered: root.settleReveal()
}
Item {
id: card
visible: root.opened && root.imagesLoaded && root.layoutSettled && root.imageArray.length > 0
visible: root.opened && root.imagesLoaded && root.imageArray.length > 0
opacity: root.layoutSettled ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 90 } }
width: Math.min(parent.width - 80, root.expandedWidth + 13 * (root.sliceWidth + root.sliceSpacing) + 40)
height: root.expandedHeight + Style.space(30) + root.bottomChromeHeight
anchors.centerIn: parent
MouseArea { anchors.fill: parent; onClicked: {} }
MouseArea { anchors.fill: parent; enabled: root.layoutSettled; onClicked: {} }
Item {
id: carousel
@@ -504,6 +676,19 @@ Item {
Keys.priority: Keys.BeforeItem
Keys.onPressed: function(event) {
if (root.deleteConfirmOpen) {
if (deleteConfirm.handleKey(event)) event.accepted = true
return
}
// Nothing is visible before the reveal: only let the user back
// out, never filter into a dead end or apply an unseen pick.
if (!root.layoutSettled) {
if (event.key === Qt.Key_Escape) {
root.cancel()
event.accepted = true
}
return
}
if (event.key === Qt.Key_Escape) {
if (root.filterText) {
root.updateFilter("")
@@ -514,6 +699,9 @@ Item {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.applySelected()
event.accepted = true
} else if (event.key === Qt.Key_Delete && !(event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier))) {
root.requestDeleteSelectedTheme()
event.accepted = true
} else if (root.filterable && Util.editsFilter(event, root.filterText)) {
root.updateFilter(Util.editedFilter(event, root.filterText))
event.accepted = true
@@ -534,6 +722,7 @@ Item {
Repeater {
id: imageCards
model: visibleImages
onCountChanged: root.maybeReveal()
delegate: Item {
id: item
@@ -547,6 +736,8 @@ Item {
readonly property bool selected: imageIndex === root.selectedIndex
readonly property bool previewReady: image.status === Image.Ready || image.status === Image.Error
readonly property bool previewSettled: !thumbnailPath || previewReady
onPreviewSettledChanged: if (previewSettled) root.maybeReveal()
onSelectedChanged: if (selected && previewReady) root.neighborImagesEnabled = true
x: selected ? carousel.previewX : (relativeIndex < 0 ? carousel.previewX + relativeIndex * carousel.itemStep : carousel.previewX + root.expandedWidth + root.sliceSpacing + (relativeIndex - 1) * carousel.itemStep)
@@ -554,10 +745,6 @@ Item {
height: selected ? root.expandedHeight : root.sliceHeight
y: selected ? 0 : (root.expandedHeight - root.sliceHeight) / 2
z: selected ? 100 : 50 - Math.min(Math.abs(relativeIndex), 40)
Behavior on x { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on y { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on width { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on height { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
readonly property real skAbs: Math.abs(root.skewOffset)
readonly property real topLeft: root.skewOffset >= 0 ? skAbs : 0
@@ -617,7 +804,6 @@ Item {
cache: false
smooth: true
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 90 } }
onStatusChanged: if (item.selected && (status === Image.Ready || status === Image.Error)) root.neighborImagesEnabled = true
}
@@ -645,6 +831,7 @@ Item {
MouseArea {
anchors.fill: parent
enabled: root.layoutSettled
cursorShape: Qt.PointingHandCursor
onClicked: item.selected ? root.applySelected() : root.select(item.imageIndex)
}
@@ -687,5 +874,20 @@ Item {
elide: Text.ElideRight
}
}
// Unlike the picker itself, the confirmation keeps the faded backdrop:
// a destructive choice should take over the screen.
ConfirmDialog {
id: deleteConfirm
anchors.fill: parent
opened: root.deleteConfirmOpen
z: 10
message: "Do you want to delete " + (root.pendingDeleteTheme ? root.labelForPath(root.pendingDeleteTheme) : "") + "?"
confirmText: "Delete"
background: root.dimColor
foreground: root.foreground
onCanceled: root.cancelDeleteTheme()
onConfirmed: root.confirmDeleteTheme()
}
}
}
@@ -6,6 +6,41 @@ function labelForPath(path) {
return nameForPath(path).replace(/[-_]+/g, " ").replace(/\b\w/g, function(match) { return match.toUpperCase() })
}
// Extra (user-installed) theme names, one per line, as listed from the user
// themes directory: the set omarchy-theme-remove can delete.
function parseThemeNames(text) {
var names = []
var lines = String(text || "").split("\n")
for (var i = 0; i < lines.length; i++) {
var name = lines[i].trim()
if (name) names.push(name)
}
return names
}
// A user directory shadowing a stock theme holds customizations, not an
// extra theme: removing it from here would silently revert to stock (and a
// shadow can even list twice, once per preview extension), so only themes
// with no stock twin can be deleted from the picker.
function canDeleteTheme(name, extraNames, stockNames) {
if (!name || !Array.isArray(extraNames) || extraNames.indexOf(name) === -1) return false
return !Array.isArray(stockNames) || stockNames.indexOf(name) === -1
}
// Where selection lands when the selected image goes away: the previous
// match in list order, or the next one when the first match is deleted.
function replacementSelectionPath(images, selectedIndex, filterText) {
var values = Array.isArray(images) ? images : []
if (selectedIndex < 0 || selectedIndex >= values.length) return ""
for (var i = selectedIndex - 1; i >= 0; i--) {
if (itemMatches(values, i, filterText)) return values[i].filePath
}
for (var i = selectedIndex + 1; i < values.length; i++) {
if (itemMatches(values, i, filterText)) return values[i].filePath
}
return ""
}
function loadRows(rows) {
var images = []
var seen = {}
@@ -125,6 +160,9 @@ if (typeof module !== "undefined") {
module.exports = {
nameForPath: nameForPath,
labelForPath: labelForPath,
parseThemeNames: parseThemeNames,
canDeleteTheme: canDeleteTheme,
replacementSelectionPath: replacementSelectionPath,
loadRows: loadRows,
itemMatches: itemMatches,
firstMatchingIndex: firstMatchingIndex,
+12 -11
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Effects
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Item {
@@ -42,8 +43,8 @@ Item {
readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0
readonly property bool errorState: failureMessage.length > 0
readonly property var inputBorderSpec: errorState
? Border.surfaceSpec("lock", "border-error", Color.lock.borderError, root.outlineThickness, "border-alpha")
: Border.surfaceSpec("lock", "border-active", Color.lock.borderActive, root.outlineThickness, "border-alpha")
? Border.surfaceSpec("lock", "border-error", Commons.Color.lock.borderError, root.outlineThickness, "border-alpha")
: Border.surfaceSpec("lock", "border-active", Commons.Color.lock.borderActive, root.outlineThickness, "border-alpha")
readonly property bool video: Util.isVideoPath(root.backgroundPath)
readonly property bool feedActive: root.video && root.loadBackground && !root.displaysBlank && !root.powerSaverActive
@@ -97,7 +98,7 @@ Item {
Rectangle {
anchors.fill: parent
color: Color.background
color: Commons.Color.background
BackgroundMedia {
id: wallpaper
@@ -156,7 +157,7 @@ Item {
width: root.fieldWidth
height: root.fieldHeight
anchors.centerIn: parent
color: Color.lock.background
color: Commons.Color.lock.background
borderSpec: root.inputBorderSpec
radius: Style.cornerRadius
clip: true
@@ -179,16 +180,16 @@ Item {
echoMode: TextInput.Password
passwordCharacter: "\u25CF"
passwordMaskDelay: 0
color: Color.lock.text
selectionColor: Color.lock.selection
selectedTextColor: Color.lock.text
color: Commons.Color.lock.text
selectionColor: Commons.Color.lock.selection
selectedTextColor: Commons.Color.lock.text
font.family: Style.font.family
font.pixelSize: text.length > 0 ? Math.max(1, Math.floor(root.passwordDotFontSize * root.passwordDotScale)) : root.fieldFontSize
font.letterSpacing: text.length > 0 ? root.passwordDotLetterSpacing * root.passwordDotScale : 0
cursorVisible: activeFocus && root.showPasswordCursor && text.length > 0
cursorDelegate: Rectangle {
width: 2
color: Color.lock.text
color: Commons.Color.lock.text
visible: passwordInput.cursorVisible
}
@@ -224,7 +225,7 @@ Item {
anchors.fill: passwordInput
text: root.authenticatingPassword ? "Checking…" : (root.failureMessage.length > 0 ? root.failureMessage : root.placeholderText)
visible: passwordInput.text.length === 0
color: root.authenticatingPassword ? Color.lock.text : (root.failureMessage.length > 0 ? Color.lock.textError : Color.lock.placeholder)
color: root.authenticatingPassword ? Commons.Color.lock.text : (root.failureMessage.length > 0 ? Commons.Color.lock.textError : Commons.Color.lock.placeholder)
font.family: Style.font.family
font.pixelSize: root.fieldFontSize
font.italic: !root.authenticatingPassword && root.failureMessage.length > 0
@@ -247,7 +248,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
visible: root.fingerprintConfigured
text: root.fingerprintUnavailable ? "󰺱" : "󰈷"
color: root.fingerprintUnavailable ? Color.lock.textError : Color.lock.placeholder
color: root.fingerprintUnavailable ? Commons.Color.lock.textError : Commons.Color.lock.placeholder
font.family: Style.font.family
font.pixelSize: Math.round(root.fieldFontSize * 1.1)
horizontalAlignment: Text.AlignHCenter
@@ -266,7 +267,7 @@ Item {
anchors.horizontalCenter: inputField.horizontalCenter
visible: root.fingerprintConfigured && root.fingerprintUnavailable
text: "Fingerprint reader unavailable"
color: Color.lock.textError
color: Commons.Color.lock.textError
font.family: Style.font.family
font.pixelSize: Style.font.heading
font.italic: true
+2 -1
View File
@@ -4,6 +4,7 @@ import Quickshell.Io
import Quickshell.Services.Pam
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import "FingerprintModel.js" as FingerprintModel
Item {
@@ -467,7 +468,7 @@ Item {
WlSessionLockSurface {
id: lockSurface
color: Color.background
color: Commons.Color.background
LockView {
id: lockView
+11 -10
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "MenuModel.js" as MenuModel
@@ -81,17 +82,17 @@ Item {
property bool deleteConfirmOpen: false
property var deleteTarget: null
onOpenedChanged: if (!opened) { deleteConfirmOpen = false; deleteTarget = null }
// Bound to the central [menu] section in shell.toml via Color.qml.
// Bound to the central [menu] section in shell.toml via Commons.Color.qml.
// Each color already includes its alpha companion (composed in the
// singleton), so consumers can drop them straight into a Rectangle.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color selectedBorder: Color.menu.selectedBorder
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
property color selectedBorder: Commons.Color.menu.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("menu", "selected-border", selectedBorder, 0)
readonly property real rowReservedBorderLeft: Border.left(selectedBorderSpec)
readonly property real rowReservedBorderRight: Border.right(selectedBorderSpec)
@@ -1075,7 +1076,7 @@ Item {
// then on the card grows and shrinks downward instead of re-centering
// on every resize, which made the menu jump around. The rows height is
// frozen at the same moment, so the starting menu also caps how tall the
// card may grow from there. Closing unfreezes both.
// card may grow from there. Closing or changing screens unfreezes both.
property int cardTop: -1
property int maxRowsHeight: -1
readonly property int centeredTop: Math.max(Style.gapsOut, Math.round((height - root.cardHeight) / 2))
@@ -1086,8 +1087,8 @@ Item {
maxRowsHeight = root.visibleRowsHeight
}
}
// The surface stays mapped between opens, so closing is shown going false.
onShownChanged: if (!shown) { cardTop = -1; maxRowsHeight = -1 }
onTargetScreenChanged: { cardTop = -1; maxRowsHeight = -1 }
Rectangle {
anchors.fill: parent
+1
View File
@@ -398,6 +398,7 @@ var GUARD_READERS = [
"omarchy-default-browser",
"omarchy-default-editor",
"omarchy-default-terminal",
"omarchy-default-dictation",
"omarchy-dns"
]
@@ -6,6 +6,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "../NotificationLogic.js" as NotificationLogic
@@ -48,10 +49,10 @@ BorderSurface {
readonly property string sanitizedBody: sanitizeBody(body)
readonly property string styledBody: NotificationLogic.styledBody(body, app, appIcon)
readonly property color dimColor: Qt.darker(Color.notifications.text, 1.4)
readonly property color bodyColor: Qt.darker(Color.notifications.text, 1.15)
readonly property color accentColor: urgency === 2 ? Color.urgent : (urgency === 0 ? dimColor : Color.notifications.countdown)
readonly property var cardBorderSpec: Border.surfaceSpec("notifications", "border", Color.notifications.border, Math.max(1, Style.space(2)))
readonly property color dimColor: Qt.darker(Commons.Color.notifications.text, 1.4)
readonly property color bodyColor: Qt.darker(Commons.Color.notifications.text, 1.15)
readonly property color accentColor: urgency === 2 ? Commons.Color.urgent : (urgency === 0 ? dimColor : Commons.Color.notifications.countdown)
readonly property var cardBorderSpec: Border.surfaceSpec("notifications", "border", Commons.Color.notifications.border, Math.max(1, Style.space(2)))
function sanitizeBody(s) {
return NotificationLogic.sanitizeBody(s, app, appIcon)
@@ -70,7 +71,7 @@ BorderSurface {
// doesn't push content under the bottom edge.
implicitHeight: mainColumn.implicitHeight + borderTop + borderBottom
radius: cornerRadius
color: Color.notifications.background
color: Commons.Color.notifications.background
borderSpec: cardBorderSpec
clip: true
@@ -139,7 +140,7 @@ BorderSurface {
anchors.centerIn: parent
visible: root.hasGlyph && smallIconImage.status !== Image.Ready
text: root.glyph
color: Color.notifications.text
color: Commons.Color.notifications.text
font.family: root.fontFamily
font.pixelSize: Style.font.displayLarge
}
@@ -150,7 +151,7 @@ BorderSurface {
Layout.alignment: Qt.AlignVCenter
visible: root.compactGlyph
text: root.glyph
color: Color.notifications.text
color: Commons.Color.notifications.text
font.family: root.fontFamily
font.pixelSize: Style.font.icon
}
@@ -172,7 +173,7 @@ BorderSurface {
visible: root.summary.length > 0
text: root.summary
font.family: "Liberation Sans"
color: Color.notifications.text
color: Commons.Color.notifications.text
font.pixelSize: Style.font.title
font.bold: true
wrapMode: Text.WordWrap
@@ -235,7 +236,7 @@ BorderSurface {
Text {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? Color.notifications.text : root.dimColor
color: closeArea.containsMouse ? Commons.Color.notifications.text : root.dimColor
font.pixelSize: Math.round(Style.font.caption * 1.44)
}
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "OsdModel.js" as OsdModel
@@ -139,8 +140,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.space(67)
color: Util.alpha(Color.background, 0.97)
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
color: Util.alpha(Commons.Color.background, 0.97)
borderSpec: Border.surfaceSpec("popups", "border", Commons.Color.popups.border, Math.max(1, Style.space(2)))
radius: Style.cornerRadius
opacity: root.opened ? 1 : 0
@@ -162,7 +163,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: root.icon
font: iconMetrics.font
color: Color.popups.text
color: Commons.Color.popups.text
}
}
Rectangle {
@@ -170,11 +171,11 @@ Item {
width: root.barWidth
height: Math.max(Style.space(6), Style.spacing.sm)
anchors.verticalCenter: parent.verticalCenter
color: Util.alpha(Color.popups.text, 0.45)
color: Util.alpha(Commons.Color.popups.text, 0.45)
Rectangle {
height: parent.height
width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0)
color: Color.accent
color: Commons.Color.accent
Behavior on width {
enabled: root.opened
@@ -192,7 +193,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: root.message
font: messageMetrics.font
color: Color.popups.text
color: Commons.Color.popups.text
elide: Text.ElideRight
maximumLineCount: 1
}
+3 -2
View File
@@ -6,6 +6,7 @@ import Quickshell.Services.Mpris
import Quickshell.Services.Pipewire
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -179,10 +180,10 @@ Panel {
readonly property string toggleHint: anyAudible ? "Mute" : "Unmute"
readonly property color hoverFill: bar
? Style.hoverFillFor(bar.foreground, Color.accent)
? Style.hoverFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
readonly property color selectedFill: bar
? Style.selectedFillFor(bar.foreground, Color.accent)
? Style.selectedFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
function sectionCount(section) {
+3 -2
View File
@@ -6,6 +6,7 @@ import Quickshell.Bluetooth
import Quickshell.Services.Pipewire
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -104,10 +105,10 @@ Panel {
readonly property string toggleHint: root.adapter && root.adapter.enabled ? "Turn Bluetooth off" : "Turn Bluetooth on"
readonly property color hoverFill: bar
? Style.hoverFillFor(bar.foreground, Color.accent)
? Style.hoverFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
readonly property color selectedFill: bar
? Style.selectedFillFor(bar.foreground, Color.accent)
? Style.selectedFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
function sectionCount(section) {
+9 -8
View File
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
@@ -75,7 +76,7 @@ Panel {
// Guarded so the widget renders before the bar is injected (the bar-widget
// contract instantiates it bare).
readonly property color contentForeground: bar ? bar.foreground : Color.foreground
readonly property color contentForeground: bar ? bar.foreground : Commons.Color.foreground
readonly property string contentFontFamily: bar ? bar.fontFamily : Style.font.family
readonly property int cellWidth: Style.space(52)
@@ -302,7 +303,7 @@ Panel {
anchors.baseline: heroDate.baseline
text: "󰃭"
color: heroMouse.containsMouse
? Style.hoverStateColor(root.contentForeground, Color.accent)
? Style.hoverStateColor(root.contentForeground, Commons.Color.accent)
: root.contentForeground
font.family: root.contentFontFamily
// Decorative, and deliberately outside the Style.font.*
@@ -317,7 +318,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
text: Qt.formatDate(root.today, "MMMM d")
color: heroMouse.containsMouse
? Style.hoverStateColor(root.contentForeground, Color.accent)
? Style.hoverStateColor(root.contentForeground, Commons.Color.accent)
: root.contentForeground
font.family: root.contentFontFamily
font.pixelSize: 52
@@ -455,7 +456,7 @@ Panel {
width: Math.round(parent.width * root.yearDone)
height: parent.height
radius: parent.radius
color: Style.selectedStateColor(root.contentForeground, Color.accent)
color: Style.selectedStateColor(root.contentForeground, Commons.Color.accent)
Behavior on width { NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic } }
}
@@ -513,7 +514,7 @@ Panel {
width: Math.round(parent.width * root.lifeDone)
height: parent.height
radius: parent.radius
color: Style.selectedStateColor(root.contentForeground, Color.accent)
color: Style.selectedStateColor(root.contentForeground, Commons.Color.accent)
Behavior on width { NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic } }
}
@@ -575,14 +576,14 @@ Panel {
height: Style.space(16)
radius: Style.cornerRadius
color: weekStartMouse.containsMouse
? Style.hoverFillFor(root.contentForeground, Color.accent)
? Style.hoverFillFor(root.contentForeground, Commons.Color.accent)
: "transparent"
Text {
anchors.centerIn: parent
text: "W"
color: weekStartMouse.containsMouse
? Style.hoverStateColor(root.contentForeground, Color.accent)
? Style.hoverStateColor(root.contentForeground, Commons.Color.accent)
: Qt.darker(root.contentForeground, 1.9)
font.family: root.contentFontFamily
font.pixelSize: Style.font.caption
@@ -667,7 +668,7 @@ Panel {
// over a grid this quiet.
color: "transparent"
border.width: modelData.today ? Style.spacing.hairline : 0
border.color: Style.normalBorderFor(root.contentForeground, Color.accent)
border.color: Style.normalBorderFor(root.contentForeground, Commons.Color.accent)
Text {
textFormat: Text.PlainText
+2 -1
View File
@@ -1,12 +1,13 @@
import QtQuick
import QtQuick.Shapes
import qs.Commons
import qs.Commons as Commons
Item {
id: root
property real iconSize: Style.font.icon
property color color: Color.foreground
property color color: Commons.Color.foreground
width: iconSize * 1.18
height: iconSize
+3 -2
View File
@@ -4,6 +4,7 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
@@ -32,8 +33,8 @@ Panel {
"Cataloging chaos"
]
readonly property string heroPhraseText: activePhrases[phraseIndex % activePhrases.length]
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color urgent: bar ? bar.urgent : Color.urgent
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property color urgent: bar ? bar.urgent : Commons.Color.urgent
readonly property color dim: Qt.darker(foreground, 1.55)
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property color iconColor: dropbox.authenticated && dropbox.active ? foreground : dim
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
import "Arc.js" as Arc
// One line of text arched over what sits below it, each character turned to
@@ -13,7 +14,7 @@ Item {
property real rise: 6
property string fontFamily: Style.font.family
property int pixelSize: Style.font.caption
property color color: Color.foreground
property color color: Commons.Color.foreground
FontMetrics {
id: metrics
+4 -3
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
import "Greetings.js" as Greet
@@ -52,7 +53,7 @@ Rectangle {
// arrow keys never reads darker than the daytime rows around it.
readonly property bool picked: panel.focusIndex === index && !panel.addSelected
readonly property bool lit: rowHover.hovered || picked
color: Model.mix(Color.popups.background, foreground,
color: Model.mix(Commons.Color.popups.background, foreground,
picked ? dayFill + hoverLift : rowHover.hovered ? phaseFill + hoverLift : phaseFill)
// Transforms leave the Column's layout alone: the knock that clears the
@@ -297,8 +298,8 @@ Rectangle {
anchors.verticalCenter: parent.top
size: fontSize + (Style.space(22) - fontSize) * 0.85
radius: size / 2
color: Model.mix(Color.popups.background, row.foreground, _hot ? 0.24 : 0.14)
borderSpec: Border.flat(Model.mix(Color.popups.background, row.foreground, _hot ? 0.65 : 0.35), Style.space(1))
color: Model.mix(Commons.Color.popups.background, row.foreground, _hot ? 0.24 : 0.14)
borderSpec: Border.flat(Model.mix(Commons.Color.popups.background, row.foreground, _hot ? 0.65 : 0.35), Style.space(1))
iconText: "\u00d7"
tooltipText: "Remove"
foreground: row.dim
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
@@ -20,7 +21,7 @@ Column {
property string placeholderText: "Search cities\u2026"
property string loadingText: "Loading zones\u2026"
property real fontSize: Style.font.bodySmall
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color dim: Qt.darker(foreground, 1.55)
property color fainter: Qt.darker(foreground, 2.1)
property string fontFamily: Style.font.family
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
import "Sun.js" as Sun
@@ -22,7 +23,7 @@ Item {
property bool hovered: false
property bool hour24: false
property real moonPhase: 0
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color fainter: Qt.darker(foreground, 2.1)
property color daylightMarker: "#E5C736"
property string fontFamily: Style.font.family
+9 -8
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "GlobeModel.js" as Solar
import "Model.js" as Model
@@ -11,14 +12,14 @@ import "Model.js" as Model
Item {
id: root
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color dim: Qt.darker(foreground, 1.55)
property color fainter: Qt.darker(foreground, 2.1)
property color daylightMarker
// Passed in so the footer's moon agrees with the rows' strips.
property real moonPhase: 0.5
// Night dots are dark so they read against the bright continents.
readonly property color nightMarker: Util.alpha(Color.background, 0.92)
readonly property color nightMarker: Util.alpha(Commons.Color.background, 0.92)
property string fontFamily: Style.font.family
property bool hour24: false
// The panel's offset setting, and the offset "home" is measured from.
@@ -30,7 +31,7 @@ Item {
property real chromeOpacity: 1
// Opaque tones mixed against the background, so the rows behind never show through.
readonly property color surfaceBase: Color.popups.background
readonly property color surfaceBase: Commons.Color.popups.background
// Light names over sea, dark over land, split at the coastline.
readonly property color seaInk: foreground
readonly property color landInk: surfaceBase
@@ -470,26 +471,26 @@ Item {
ctx.fillStyle = day ? root.daylightMarker : root.nightMarker
ctx.fill()
ctx.lineWidth = root.scaled(1.2)
ctx.strokeStyle = day ? Util.alpha(Color.background, 0.7) : Util.alpha(fg, 0.85)
ctx.strokeStyle = day ? Util.alpha(Commons.Color.background, 0.7) : Util.alpha(fg, 0.85)
ctx.stroke()
if (root.isHome(i)) {
ctx.beginPath()
ctx.arc(p.x, p.y, root.scaled(3.4), 0, Math.PI * 2)
ctx.fillStyle = Color.accent
ctx.fillStyle = Commons.Color.accent
ctx.fill()
ctx.lineWidth = root.scaled(1.2)
ctx.strokeStyle = Util.alpha(Color.background, 0.5)
ctx.strokeStyle = Util.alpha(Commons.Color.background, 0.5)
ctx.stroke()
ctx.beginPath()
ctx.arc(p.x, p.y, root.scaled(6.4), 0, Math.PI * 2)
ctx.lineWidth = root.scaled(1.4)
ctx.strokeStyle = Util.alpha(Color.accent, 0.65)
ctx.strokeStyle = Util.alpha(Commons.Color.accent, 0.65)
ctx.stroke()
} else if (root.isTracked(i)) {
ctx.beginPath()
ctx.arc(p.x, p.y, root.scaled(4.6), 0, Math.PI * 2)
ctx.lineWidth = root.scaled(1.3)
ctx.strokeStyle = Color.accent
ctx.strokeStyle = Commons.Color.accent
ctx.stroke()
}
if (isSel) {
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// The selected city under the globe: name, local time and badge, then its
// zone and offset. Blank without a selection; the parent reserves the height.
@@ -12,7 +13,7 @@ Item {
property string offsetLabel: ""
property string badge: "" // "home", "tracked" or ""
property real moonPhase: 0.5
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color dim: Qt.darker(foreground, 1.55)
property color fainter: Qt.darker(foreground, 2.1)
property color daylightMarker
@@ -90,7 +91,7 @@ Item {
textFormat: Text.PlainText
text: root.badge
visible: root.has && text !== ""
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
import "GlobeModel.js" as Solar
// "World [globe] Clock" under an arched caption. The little globe is the door
@@ -10,7 +11,7 @@ Column {
property string caption: ""
property color captionColor: dim
property bool captionClickable: false
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color dim: Qt.darker(foreground, 1.55)
property string fontFamily: Style.font.family
+5 -4
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import "GlobeModel.js" as Solar
import "Model.js" as Model
@@ -11,7 +12,7 @@ Item {
id: root
property real spin: 0
property color color: Color.foreground
property color color: Commons.Color.foreground
// Below this size land turns to noise; draw the graticule alone.
readonly property bool showLand: width >= 22
@@ -24,7 +25,7 @@ Item {
property bool showMarker: false
property real markerLat: 0
property real markerLon: 0
property color markerColor: Color.accent
property color markerColor: Commons.Color.accent
readonly property real radius: Math.min(width, height) / 2 - 1
readonly property string pluginDir: Quickshell.env("OMARCHY_PATH") + "/shell/plugins/panels/elsewhen"
@@ -68,7 +69,7 @@ Item {
// The ocean, opaque like the large globe.
ctx.beginPath()
ctx.arc(0, 0, r, 0, Math.PI * 2)
ctx.fillStyle = Model.mix(Color.popups.background, c, root.bold ? 0.16 : 0.13)
ctx.fillStyle = Model.mix(Commons.Color.popups.background, c, root.bold ? 0.16 : 0.13)
ctx.fill()
ctx.save()
@@ -117,7 +118,7 @@ Item {
ctx.fill()
// Edged so it does not dissolve into a continent of similar lightness.
ctx.lineWidth = Math.max(1, r * 0.04)
ctx.strokeStyle = Util.alpha(Color.background, 0.5)
ctx.strokeStyle = Util.alpha(Commons.Color.background, 0.5)
ctx.stroke()
ctx.beginPath()
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
import "GlobeModel.js" as Solar
// The night marker drawn as the moon's current phase. The faint full disc
@@ -8,7 +9,7 @@ Item {
id: root
property real phase: 0 // 0 new, 0.25 first quarter, 0.5 full
property color color: Color.foreground
property color color: Commons.Color.foreground
onPhaseChanged: canvas.requestPaint()
onColorChanged: canvas.requestPaint()
+3 -2
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
import "GlobeModel.js" as Solar
@@ -80,7 +81,7 @@ Panel {
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property color dim: Qt.darker(foreground, 1.55)
readonly property color fainter: Qt.darker(foreground, 2.1)
// A literal gold: several themes' "yellow" is not yellow.
@@ -830,7 +831,7 @@ Panel {
width: parent.width
caption: root.hereLine
// Accent while scrubbed, so a shifted time is never taken for now.
captionColor: root.scrubMinutes !== 0 ? Color.accent : root.dim
captionColor: root.scrubMinutes !== 0 ? Commons.Color.accent : root.dim
captionClickable: root.focusIndex >= 0
foreground: root.foreground
dim: root.dim
+3 -2
View File
@@ -4,6 +4,7 @@ import Quickshell
import Quickshell.Io
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -873,8 +874,8 @@ Panel {
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(monitorRow)
current: isFocused
foreground: root.bar.foreground
fill: Style.hoverFillFor(root.bar.foreground, Color.accent)
currentFill: Style.selectedFillFor(root.bar.foreground, Color.accent)
fill: Style.hoverFillFor(root.bar.foreground, Commons.Color.accent)
currentFill: Style.selectedFillFor(root.bar.foreground, Commons.Color.accent)
implicitHeight: monitorInner.implicitHeight + Style.spacing.xl
opacity: canToggle ? 1.0 : 0.45
+8 -11
View File
@@ -6,6 +6,7 @@ import Quickshell.Io
import Quickshell.Networking
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -291,8 +292,8 @@ Panel {
// `selectedIndex`). Mouse hover and keyboard nav both mutate this state
// at the root; items never read containsMouse for visuals. See
// CursorSurface for the shared chrome shared by rows and pills.
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent"
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Commons.Color.accent) : "transparent"
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Commons.Color.accent) : "transparent"
// scannerEnabled lives on the shared WifiDevice, which has no reference
// counting, and a bar widget is instantiated once per monitor. Tracking the
@@ -1245,32 +1246,28 @@ Panel {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Button {
PanelActionButton {
id: qrAction
visible: root.canShareWifi
iconText: "󰐲"
tooltipText: "Show QR code"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
iconSize: Style.font.subtitle * 1.5
horizontalPadding: Style.space(5)
verticalPadding: Style.space(2)
fontSize: Style.font.subtitle * 1.5
hasCursor: root.qrHeaderHasCursor
Layout.alignment: Qt.AlignVCenter
onHovered: function(on) { if (on) root.setHeaderCursor(root.qrHeaderIndex) }
onClicked: root.summonWifiQr()
}
Button {
PanelActionButton {
id: speedAction
visible: root.canRunSpeedTest
iconText: "󰓅"
tooltipText: "Run a speed test"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
iconSize: Style.font.subtitle * 1.5
horizontalPadding: Style.space(5)
verticalPadding: Style.space(2)
fontSize: Style.font.subtitle * 1.5
hasCursor: root.speedHeaderHasCursor
Layout.alignment: Qt.AlignVCenter
onHovered: function(on) { if (on) root.setHeaderCursor(root.speedHeaderIndex) }
@@ -2078,7 +2075,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
height: Style.spacing.controlHeight
color: Style.normalFillFor(root.bar.foreground)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Commons.Color.accent)
radius: Style.cornerRadius
Text {
+2 -1
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
@@ -88,7 +89,7 @@ Panel {
}
readonly property color batteryFillColor: {
return root.bar ? root.bar.foreground : Color.foreground
return root.bar ? root.bar.foreground : Commons.Color.foreground
}
// Cute agent-flavored phrases shown in the hero status line, rotated on a
+6 -5
View File
@@ -4,6 +4,7 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Panel {
@@ -37,8 +38,8 @@ Panel {
]
readonly property string heroPhraseText: activePhrases[phraseIndex % activePhrases.length]
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color urgent: bar ? bar.urgent : Color.urgent
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property color urgent: bar ? bar.urgent : Commons.Color.urgent
readonly property color dim: Qt.darker(foreground, 1.55)
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
@@ -54,8 +55,8 @@ Panel {
readonly property color iconColor: tailscale.active ? foreground : dim
readonly property string toggleHint: tailscale.active ? "Turn Tailscale off" : (tailscale.needsLogin ? "Authorize this device" : "Turn Tailscale on")
readonly property color barIconColor: tailscale.active ? barForeground : Qt.darker(barForeground, 1.55)
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent"
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Commons.Color.accent) : "transparent"
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Commons.Color.accent) : "transparent"
function selectedPeer() {
if (tailscale.peers.length === 0) return null
@@ -1035,7 +1036,7 @@ Panel {
}
}
background: BorderSurface {
color: Color.background
color: Commons.Color.background
borderSpec: Border.flat(root.dim, 1)
radius: Style.cornerRadius
}
@@ -1,13 +1,14 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Item {
id: root
property real iconSize: Style.font.icon
property color color: Color.foreground
property color badgeColor: Color.urgent
property color color: Commons.Color.foreground
property color badgeColor: Commons.Color.urgent
property bool crossed: false
property bool warning: false
@@ -51,12 +52,12 @@ Item {
color: root.badgeColor
anchors.right: parent.right
anchors.bottom: parent.bottom
borderSpec: Border.flat(Color.popups.background, 1)
borderSpec: Border.flat(Commons.Color.popups.background, 1)
Text {
anchors.centerIn: parent
text: "!"
color: Color.background
color: Commons.Color.background
font.family: Style.font.family
font.pixelSize: Math.max(6, parent.height * 0.72)
font.bold: true
+4 -3
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "Model.js" as Model
@@ -646,7 +647,7 @@ Panel {
height: Style.space(18)
anchors.verticalCenter: parent.verticalCenter
radius: Math.min(4, Style.cornerRadius)
color: !root.savingLocation && clearLocationArea.containsMouse ? Style.hoverFillFor(root.bar.foreground, Color.accent) : "transparent"
color: !root.savingLocation && clearLocationArea.containsMouse ? Style.hoverFillFor(root.bar.foreground, Commons.Color.accent) : "transparent"
Text {
textFormat: Text.PlainText
@@ -752,7 +753,7 @@ Panel {
width: parent.width
height: suggestionRow.implicitHeight + Style.space(12)
radius: Style.cornerRadius
color: index === root.suggestionIndex ? Style.hoverFillFor(root.bar.foreground, Color.accent) : "transparent"
color: index === root.suggestionIndex ? Style.hoverFillFor(root.bar.foreground, Commons.Color.accent) : "transparent"
Row {
id: suggestionRow
@@ -764,7 +765,7 @@ Panel {
Text {
textFormat: Text.PlainText
text: modelData.name
color: index === root.suggestionIndex ? Style.hoverStateColor(root.bar.foreground, Color.accent) : root.bar.foreground
color: index === root.suggestionIndex ? Style.hoverStateColor(root.bar.foreground, Commons.Color.accent) : root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
}
+13 -12
View File
@@ -4,6 +4,7 @@ import Quickshell.Io
import Quickshell.Services.Polkit
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "PolkitModel.js" as PolkitModel
@@ -11,14 +12,14 @@ Item {
id: root
property string fontFamily: Style.font.menuFamily
// Bound to the central [polkit] section in shell.toml via Color.qml.
property color accent: Color.polkit.accent
property color background: Color.polkit.background
property color foreground: Color.polkit.text
property color border: Color.polkit.border
property color borderError: Color.polkit.borderError
// Bound to the central [polkit] section in shell.toml via Commons.Color.qml.
property color accent: Commons.Color.polkit.accent
property color background: Commons.Color.polkit.background
property color foreground: Commons.Color.polkit.text
property color border: Commons.Color.polkit.border
property color borderError: Commons.Color.polkit.borderError
property var borderSpec: Border.surfaceSpec("polkit", errorFlash ? "border-error" : "border", errorFlash ? borderError : border, Math.max(1, Style.space(2)), "border-alpha")
property color scrim: Color.polkit.scrim
property color scrim: Commons.Color.polkit.scrim
readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding
property int fieldHeight: Math.max(Style.space(42), Style.spacing.controlHeight)
@@ -278,7 +279,7 @@ Item {
text: "\udb80\ude37"
fontFamily: root.fontFamily
fontSize: Math.round(root.fieldHeight * 0.7)
color: root.errorFlash ? Color.polkit.textError : root.accent
color: root.errorFlash ? Commons.Color.polkit.textError : root.accent
}
Row {
@@ -293,7 +294,7 @@ Item {
Text {
text: "\uf023"
color: root.errorFlash ? Color.polkit.textError : root.accent
color: root.errorFlash ? Commons.Color.polkit.textError : root.accent
font.family: root.fontFamily
font.pixelSize: Style.font.iconLarge
width: Style.space(26)
@@ -318,7 +319,7 @@ Item {
font.pixelSize: Style.font.iconLarge
echoMode: root.responseVisible ? TextInput.Normal : TextInput.Password
passwordCharacter: "\u2022"
color: root.errorFlash ? Color.polkit.textError : root.foreground
color: root.errorFlash ? Commons.Color.polkit.textError : root.foreground
cursorVisible: activeFocus && !root.submitted && !root.errorFlash
readOnly: root.submitted || root.errorFlash
enabled: root.dialogVisible
@@ -337,7 +338,7 @@ Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: root.errorFlash ? "Wrong" : (root.submitted ? "Checking..." : "Enter password")
color: root.errorFlash ? Color.polkit.textError : root.foreground
color: root.errorFlash ? Commons.Color.polkit.textError : root.foreground
opacity: root.errorFlash ? 1 : 0.36
font.family: root.fontFamily
font.pixelSize: Style.font.iconLarge
@@ -350,7 +351,7 @@ Item {
height: Style.space(24)
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: root.errorFlash ? Color.polkit.textError : root.foreground
color: root.errorFlash ? Commons.Color.polkit.textError : root.foreground
visible: passwordInput.visible && passwordInput.activeFocus && passwordInput.text.length === 0 && !root.submitted && !root.errorFlash
}
+5 -4
View File
@@ -2,6 +2,7 @@ import Quickshell
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "ReminderFlowModel.js" as ReminderFlowModel
@@ -18,11 +19,11 @@ Item {
property string filterText: ""
property string fontFamily: Style.font.menuFamily
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color scrim: Commons.Color.menu.scrim
readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding
property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2)
+5 -4
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import qs.Ui
import qs.Commons
import qs.Commons as Commons
BarWidget {
id: root
@@ -125,8 +126,8 @@ BarWidget {
width: Style.space(64)
height: Style.space(64)
radius: Style.spacing.labelGap
color: Style.normalFillFor(root.bar.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
color: Style.normalFillFor(root.bar.foreground, Commons.Color.accent)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Commons.Color.accent)
Image {
anchors.fill: parent
@@ -249,8 +250,8 @@ BarWidget {
width: sourceList.width
height: sourceInner.implicitHeight + Style.space(10)
radius: Style.spacing.labelGap
color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent"
borderSpec: selected ? Border.controlSpec("normal", root.bar.foreground, Color.accent) : Border.none()
color: selected ? Style.selectedFillFor(root.bar.foreground, Commons.Color.accent) : "transparent"
borderSpec: selected ? Border.controlSpec("normal", root.bar.foreground, Commons.Color.accent) : Border.none()
Row {
id: sourceInner
+330
View File
@@ -0,0 +1,330 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
// Keep startup and theme covers alive across background service handoffs.
Item {
id: root
property var host: null
property bool cover: true
readonly property bool sessionConsumed: String(sessionMarker.text() || "").trim() === (Quickshell.env("OMARCHY_SESSION_ID") || Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE"))
property bool startupSettled: sessionConsumed
property bool startupPending: !sessionConsumed
property bool startupMediaReady: false
property real startupOpacity: startupPending ? 1 : 0
readonly property bool desktopReady: !host || (!host.pluginReloading && (!host.pluginRegistry || !host.pluginRegistry.scanning) && (!("bar" in host) || !!host.bar))
property bool checked: false
property string themeToken: ""
property string transitionToken: ""
readonly property string startupBackgroundPath: Quickshell.env("OMARCHY_STARTUP_BACKGROUND")
property string themeBackground: sessionConsumed && !Util.isVideoPath(startupBackgroundPath) ? startupBackgroundPath : ""
property var themeNativeSize: null
property string themeColors: ""
property string themeShell: ""
property real themeOpacity: 1
readonly property var backgroundService: host && host.services ? host.firstPartyServiceFor("omarchy.background") : null
readonly property bool backgroundActive: !!backgroundService && !backgroundService.suspended
readonly property bool backgroundReady: backgroundActive && backgroundService.ready !== false
function restoreStartupCursor() {
Quickshell.execDetached(["hyprctl", "eval", "if omarchy_startup_cursor_restore then omarchy_startup_cursor_restore() end"])
}
function finishStartup() {
if (!cover || !startupSettled) return
var registry = host ? host.pluginRegistry : null
var backgroundId = registry ? registry.resolveEnabledId("omarchy.background") : ""
var disabled = registry && registry.installedPlugins[backgroundId] && !registry.isEnabled(backgroundId)
if (!backgroundReady && !disabled) return
cover = false
if (!themeToken) themeBackground = ""
startupMediaReady = true
revealStartup()
}
function revealStartup() {
if (!startupPending || startupFade.running || !startupMediaReady || !desktopReady) return
if (!startupCovers.instances.length) return
for (var panel of startupCovers.instances)
if (panel.readyFrames < 2) return
startupFade.start()
}
onDesktopReadyChanged: revealStartup()
onBackgroundReadyChanged: finishStartup()
Connections {
target: root.host && root.host.pluginRegistry ? root.host.pluginRegistry : null
function onPluginsChanged() { root.finishStartup() }
}
function prepareTheme(fromPath, token, colors, shell) {
framePoll.stop()
themeFade.stop()
themeToken = token
transitionToken = token
if (themeBackground !== fromPath) {
themeNativeSize = backgroundService && backgroundService.nativeSizes ? backgroundService.nativeSizes[backgroundService.displayedBackground] : null
}
themeBackground = fromPath
themeColors = colors
themeShell = shell
themeOpacity = 1
themeFallback.restart()
}
function revealTheme() {
if (!themeToken && !cover) return
framePoll.stop()
themeFallback.stop()
if (themeToken) {
Commons.Color.loadColors(Util.decodeBase64(themeColors))
Commons.Color.loadShell(Util.decodeBase64(themeShell))
Style.scheduleRefresh()
}
cover = false
themeToken = ""
themeColors = ""
themeShell = ""
if (startupPending) {
// The login reveal starts on moving video, without showing its final still.
themeBackground = ""
startupMediaReady = true
revealStartup()
} else {
themeFade.restart()
}
}
function finishTheme(token) {
if (token === themeToken) revealTheme()
}
function themeStatus(token) {
return token === transitionToken && (themeToken || themeFade.running || startupFade.running) ? "pending" : "ready"
}
function themeCoverStatus(token) {
if (token !== themeToken) return "superseded"
if (!themeBackground) return "ready"
for (var panel of covers.instances) {
if (panel.coverFailed) return "error"
if (!panel.coverReady) return "loading"
}
return covers.instances.length > 0 ? "ready" : "loading"
}
function cancelTheme() {
framePoll.stop()
themeFallback.stop()
themeFade.stop()
themeToken = ""
transitionToken = ""
themeBackground = ""
themeColors = ""
themeShell = ""
}
NumberAnimation {
id: startupFade
target: root
property: "startupOpacity"
to: 0
duration: Style.duration(420)
easing.type: Easing.OutCubic
onStarted: root.restoreStartupCursor()
onFinished: root.startupPending = false
}
// A missing wallpaper or failed plugin must not leave applications hidden.
Timer {
interval: 10000
running: root.startupPending
onTriggered: {
root.cover = false
if (!root.themeToken) root.themeBackground = ""
root.startupMediaReady = true
startupFade.start()
}
}
NumberAnimation {
id: themeFade
target: root
property: "themeOpacity"
to: 0
duration: Style.duration(420)
easing.type: Easing.OutCubic
onFinished: root.themeBackground = ""
}
Timer {
id: themeFallback
interval: 10000
onTriggered: root.revealTheme()
}
// A renderer left over from another intro can first fade from its still.
// Keep that preparation hidden until the actual video is fully revealed.
Timer {
id: framePoll
interval: 16
repeat: true
onTriggered: if (!frameStatus.running) frameStatus.running = true
}
Process {
id: frameStatus
command: ["owe", "render-status"]
property string token: ""
onStarted: token = root.themeToken
stdout: StdioCollector { id: frameStatusOut }
onExited: function(exitCode) {
if (exitCode !== 0 || token !== root.themeToken || (!token && !root.cover)) return
try {
var status = JSON.parse(frameStatusOut.text)
if (status.kind === "video" && status.ready && !status.has_transition && status.time_pos > 0)
root.revealTheme()
} catch (e) {}
}
}
// A shell restart shares this compositor session; a new login does not.
FileView {
id: sessionMarker
path: Quickshell.env("HOME") + "/.local/state/omarchy/background-intro.session-id"
blockLoading: true
watchChanges: false
printErrors: false
}
Process {
id: startupBackground
command: ["readlink", "-f", Quickshell.env("HOME") + "/.local/state/omarchy/current/background"]
stdout: StdioCollector { id: startupBackgroundOut }
onExited: function(exitCode) {
var path = String(startupBackgroundOut.text || "").trim()
if (exitCode === 0 && root.cover && !root.startupPending && !root.themeToken && !Util.isVideoPath(path))
root.themeBackground = path
}
}
Component.onCompleted: {
checked = true
if (sessionConsumed) restoreStartupCursor()
if (!startupBackgroundPath && !startupPending) startupBackground.running = true
introProc.running = true
}
Component.onDestruction: if (startupPending) restoreStartupCursor()
onBackgroundActiveChanged: {
if (!backgroundActive && checked) {
if (cover || themeToken) {
framePoll.start()
if (!frameStatus.running) frameStatus.running = true
}
}
}
// Cover the bar as well as the wallpaper on a new login. Shell restarts
// retain the current desktop and never map this startup cover.
Variants {
id: startupCovers
model: Quickshell.screens
PanelWindow {
required property var modelData
property int readyFrames: 0
screen: modelData
visible: root.startupPending
color: "transparent"
mask: Region {}
anchors { top: true; bottom: true; left: true; right: true }
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.namespace: "omarchy-background"
Rectangle {
anchors.fill: parent
color: "black"
opacity: root.startupOpacity
}
FrameAnimation {
running: root.startupPending && root.desktopReady && readyFrames < 2
onTriggered: {
readyFrames += 1
if (readyFrames === 2) root.revealStartup()
}
}
}
}
Variants {
id: covers
model: Quickshell.screens
PanelWindow {
id: panel
required property var modelData
property int coverFrames: 0
readonly property bool coverReady: outgoingFrame.status === Image.Ready && coverFrames >= 2
readonly property bool coverFailed: outgoingFrame.status === Image.Error
screen: modelData
visible: (!root.startupPending && root.cover) || root.themeBackground !== ""
// Keep the window transparent throughout the fade. Paint the startup
// color inside it so changing the window format cannot flash black.
color: "transparent"
mask: Region {}
anchors { top: true; bottom: true; left: true; right: true }
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.namespace: "omarchy-background"
Rectangle {
anchors.fill: parent
visible: root.cover
color: Commons.Color.background
}
Image {
id: outgoingFrame
anchors.fill: parent
source: root.themeBackground ? Util.fileUrl(root.themeBackground) : ""
sourceSize: {
var w = Math.ceil((parent.width || modelData.width) * modelData.devicePixelRatio)
var h = Math.ceil((parent.height || modelData.height) * modelData.devicePixelRatio)
var native = root.themeNativeSize
return native && (native.width < w || native.height < h) ? Qt.size(native.width, native.height) : Qt.size(w, h)
}
fillMode: Image.PreserveAspectCrop
opacity: root.themeOpacity
// Start decoding while the remaining theme configs render.
asynchronous: true
onStatusChanged: panel.coverFrames = 0
}
// Give the ready image a frame to reach the compositor before OWE
// releases the shell's wallpaper underneath this cover.
FrameAnimation {
running: outgoingFrame.status === Image.Ready && panel.coverFrames < 2
onTriggered: panel.coverFrames += 1
}
}
}
Process {
id: introProc
command: ["omarchy-theme-bg-boot-intro"]
onExited: {
root.startupSettled = true
root.finishStartup()
}
}
}
+28 -2
View File
@@ -5,6 +5,7 @@ import Quickshell.Hyprland
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import "plugins/bar"
import "services"
@@ -21,6 +22,7 @@ ShellRoot {
property BarWidgetRegistry barWidgetRegistry: BarWidgetRegistry { }
property AppLibrary appLibrary: AppLibrary { }
property BrightnessKeys brightnessKeys: BrightnessKeys { host: shell }
property BackgroundIntro bootIntro: BackgroundIntro { host: shell }
property string home: Quickshell.env("HOME")
@@ -1704,6 +1706,7 @@ ShellRoot {
function ping(): string {
return "ok"
}
}
// ---------------------------------------------------------- shell IPC
@@ -1711,17 +1714,40 @@ ShellRoot {
ShellIpc {
target: "shell"
function prepareThemeIntro(fromPath: string, token: string, colorsB64: string, shellB64: string): void {
shell.bootIntro.prepareTheme(fromPath, token, colorsB64, shellB64)
}
function finishThemeIntro(token: string): void {
shell.bootIntro.finishTheme(token)
}
function themeIntroStatus(token: string): string {
return shell.bootIntro.themeStatus(token)
}
function themeIntroCoverStatus(token: string): string {
return shell.bootIntro.themeCoverStatus(token)
}
function ping(): string {
return "ok"
}
function applyTheme(colorsB64: string, shellB64: string): string {
if (shell.bootIntro) shell.bootIntro.cancelTheme()
var background = shell.firstPartyServiceFor("omarchy.background")
if (background && typeof background.setPendingTheme === "function" && typeof background.applyPendingTheme === "function") {
background.setPendingTheme(colorsB64, shellB64)
background.applyPendingTheme()
return "ok"
}
var colorsRaw = ""
var shellRaw = ""
try { colorsRaw = Qt.atob(String(colorsB64 || "")) } catch (e) { colorsRaw = "" }
try { shellRaw = Qt.atob(String(shellB64 || "")) } catch (e2) { shellRaw = "" }
Color.loadColors(colorsRaw)
Color.loadShell(shellRaw)
Commons.Color.loadColors(colorsRaw)
Commons.Color.loadShell(shellRaw)
Style.scheduleRefresh()
return "ok"
}