Add visual bar customizer
Each layout entry is now an object — {id, ...inline settings} — instead
of a (name, separate modules map) pair. Multiple instances of the same
widget are trivially supported (spacer is the main beneficiary). Settings
travel with the entry, so reordering keeps them attached.
shell.qml:
- normalizeLayout converts string entries to {id} and drops invalid ones
- ModuleSlot binds to a full entry; moduleName and moduleSettings derive
from it via entryId/entrySettings helpers
- centerAnchor lookup walks layoutEntries directly
- builtinBarConfig defaults use the object form
- README + bar-defaults.json updated to the new schema
bar-settings/ (new Quickshell config):
- Top toolbar with Reset / Save and Position / centerAnchor dropdowns
- Three section editors (left/center/right) with current widgets shown
as cards: name + description + move up/down/settings/remove buttons
- '+ Add widget' menu per section, sorted alphabetically by display name
(only spacer can repeat; everything else is one per bar)
- Per-widget settings dialog (FloatingWindow) loads an inline schema for
widgets in the catalog. Initial schemas: spacer (size), calendar
(formats), brightness (step)
- FileView { atomicWrites } writes to ~/.config/omarchy/bar.json on Save
omarchy-launch-bar-settings + hypr window rules pin the settings window
to a 760px floating square. controlCenter gains a 'Customize bar…' button
that launches it.
This commit is contained in:
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Launch the Omarchy bar customizer
|
||||||
|
# omarchy:group=launch
|
||||||
|
# omarchy:name=bar-settings
|
||||||
|
# omarchy:examples=omarchy launch bar-settings
|
||||||
|
|
||||||
|
OMARCHY_PATH="${OMARCHY_PATH:-$HOME/.local/share/omarchy}"
|
||||||
|
CONFIG_DIR="$OMARCHY_PATH/default/quickshell/bar-settings"
|
||||||
|
|
||||||
|
if quickshell list -p "$CONFIG_DIR" 2>/dev/null | grep -q '^Instance '; then
|
||||||
|
hyprctl dispatch focuswindow 'title:Omarchy bar settings' >/dev/null 2>&1
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
setsid uwsm-app -- env OMARCHY_PATH="$OMARCHY_PATH" quickshell -p "$CONFIG_DIR" >/dev/null 2>&1 &
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
hl.window_rule({ match = { title = "^Omarchy bar settings$" }, tag = "+floating-window" })
|
||||||
|
hl.window_rule({ match = { title = "^Omarchy bar settings$" }, size = { 760, 760 } })
|
||||||
|
hl.window_rule({ match = { title = "^Widget settings " }, tag = "+floating-window" })
|
||||||
|
hl.window_rule({ match = { title = "^Widget settings " }, size = { 420, 360 } })
|
||||||
@@ -0,0 +1,882 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
ShellRoot {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string home: Quickshell.env("HOME")
|
||||||
|
property string omarchyPath: Quickshell.env("OMARCHY_PATH") || (home + "/.local/share/omarchy")
|
||||||
|
readonly property string userConfigPath: home + "/.config/omarchy/bar.json"
|
||||||
|
readonly property string defaultsPath: omarchyPath + "/default/quickshell/bar/bar-defaults.json"
|
||||||
|
|
||||||
|
property color foreground: "#cacccc"
|
||||||
|
property color background: "#101315"
|
||||||
|
property color accent: "#cacccc"
|
||||||
|
property color urgent: "#a55555"
|
||||||
|
|
||||||
|
property string fontFamily: "JetBrainsMono Nerd Font"
|
||||||
|
|
||||||
|
property var defaultConfig: ({})
|
||||||
|
property var draft: ({ position: "top", centerAnchor: "calendar", layout: { left: [], center: [], right: [] }, fontFamily: "JetBrainsMono Nerd Font" })
|
||||||
|
property var registry: ({})
|
||||||
|
property bool dirty: false
|
||||||
|
property int draftRevision: 0
|
||||||
|
|
||||||
|
function cloneJson(value) {
|
||||||
|
return JSON.parse(JSON.stringify(value || null))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeConfig(base, override) {
|
||||||
|
var result = cloneJson(base || {})
|
||||||
|
if (!isPlainObject(override)) return result
|
||||||
|
for (var key in override) {
|
||||||
|
if (isPlainObject(result[key]) && isPlainObject(override[key]))
|
||||||
|
result[key] = mergeConfig(result[key], override[key])
|
||||||
|
else
|
||||||
|
result[key] = cloneJson(override[key])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayoutEntry(entry) {
|
||||||
|
if (typeof entry === "string") return { id: entry }
|
||||||
|
if (isPlainObject(entry) && entry.id) return cloneJson(entry)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayout(layout) {
|
||||||
|
var sections = ["left", "center", "right"]
|
||||||
|
var result = {}
|
||||||
|
for (var i = 0; i < sections.length; i++) {
|
||||||
|
var s = sections[i]
|
||||||
|
var arr = []
|
||||||
|
var src = (layout && layout[s]) || []
|
||||||
|
for (var j = 0; j < src.length; j++) {
|
||||||
|
var entry = normalizeLayoutEntry(src[j])
|
||||||
|
if (entry) arr.push(entry)
|
||||||
|
}
|
||||||
|
result[s] = arr
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
var defaults = defaultsFile.text() ? JSON.parse(defaultsFile.text()) : {}
|
||||||
|
defaultConfig = defaults
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Bad defaults JSON:", e)
|
||||||
|
defaultConfig = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userText = userFile.text() || "{}"
|
||||||
|
var user = {}
|
||||||
|
try { user = JSON.parse(userText) } catch (e) { user = {} }
|
||||||
|
|
||||||
|
var merged = mergeConfig(defaultConfig, user)
|
||||||
|
draft = {
|
||||||
|
position: String(merged.position || "top"),
|
||||||
|
centerAnchor: String(merged.centerAnchor || ""),
|
||||||
|
fontFamily: String(merged.fontFamily || "JetBrainsMono Nerd Font"),
|
||||||
|
layout: normalizeLayout(merged.layout || {})
|
||||||
|
}
|
||||||
|
dirty = false
|
||||||
|
draftRevision++
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
var payload = cloneJson(draft)
|
||||||
|
userFile.setText(JSON.stringify(payload, null, 2) + "\n")
|
||||||
|
dirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefaults() {
|
||||||
|
userFile.setText("{}\n")
|
||||||
|
loadConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
function markDirty() {
|
||||||
|
dirty = true
|
||||||
|
draftRevision++
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveEntry(section, fromIndex, toIndex) {
|
||||||
|
var arr = draft.layout[section].slice()
|
||||||
|
if (toIndex < 0 || toIndex >= arr.length) return
|
||||||
|
var item = arr[fromIndex]
|
||||||
|
arr.splice(fromIndex, 1)
|
||||||
|
arr.splice(toIndex, 0, item)
|
||||||
|
draft.layout[section] = arr
|
||||||
|
markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEntry(section, index) {
|
||||||
|
var arr = draft.layout[section].slice()
|
||||||
|
arr.splice(index, 1)
|
||||||
|
draft.layout[section] = arr
|
||||||
|
markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEntry(section, id) {
|
||||||
|
var arr = draft.layout[section].slice()
|
||||||
|
arr.push({ id: id })
|
||||||
|
draft.layout[section] = arr
|
||||||
|
markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEntry(section, index, newEntry) {
|
||||||
|
var arr = draft.layout[section].slice()
|
||||||
|
arr[index] = cloneJson(newEntry)
|
||||||
|
draft.layout[section] = arr
|
||||||
|
markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTheme(raw) {
|
||||||
|
var lines = String(raw || "").split("\n")
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var match = lines[i].match(/^\s*([A-Za-z0-9_-]+)\s*=\s*["']?(#[0-9A-Fa-f]{6})/)
|
||||||
|
if (!match) continue
|
||||||
|
if (match[1] === "foreground") foreground = match[2]
|
||||||
|
else if (match[1] === "background") background = match[2]
|
||||||
|
else if (match[1] === "color4" || match[1] === "accent") accent = match[2]
|
||||||
|
else if (match[1] === "red") urgent = match[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catalog of available widgets — id → display metadata + settings schema path.
|
||||||
|
// settingsForm is an inline-defined Component name that opens when editing.
|
||||||
|
readonly property var catalog: ({
|
||||||
|
"omarchy": { name: "Omarchy menu", description: "Launches the Omarchy menu" },
|
||||||
|
"workspaces": { name: "Workspaces (legacy)", description: "Workspace numbers, no animation" },
|
||||||
|
"workspacesPro": { name: "Workspaces", description: "Animated workspace switcher" },
|
||||||
|
"activeWindow": { name: "Active window", description: "Title of the focused window" },
|
||||||
|
"clock": { name: "Clock", description: "Date / time text" },
|
||||||
|
"calendar": { name: "Calendar", description: "Clock with month-grid popup", settingsForm: "calendarSettings" },
|
||||||
|
"media": { name: "Media", description: "MPRIS now-playing with controls" },
|
||||||
|
"weather": { name: "Weather (legacy)", description: "Tiny weather pill" },
|
||||||
|
"weatherFlyout": { name: "Weather", description: "Weather pill with detail popup" },
|
||||||
|
"update": { name: "Updates", description: "Indicates available system updates" },
|
||||||
|
"voxtype": { name: "Voxtype", description: "Voxtype dictation state" },
|
||||||
|
"screenRecording": { name: "Screen recording", description: "Active recording indicator" },
|
||||||
|
"idle": { name: "Idle (legacy)", description: "Inhibitor indicator" },
|
||||||
|
"idleInhibitor": { name: "Keep awake", description: "Idle inhibitor toggle" },
|
||||||
|
"notifications": { name: "DND (mako)", description: "Notification silencing indicator" },
|
||||||
|
"notificationCenter": { name: "Notification center", description: "Recent notifications + DND (replaces mako)" },
|
||||||
|
"tray": { name: "System tray", description: "Status notifier items" },
|
||||||
|
"bluetooth": { name: "Bluetooth (legacy)", description: "Bluetooth status icon" },
|
||||||
|
"bluetoothPanel": { name: "Bluetooth", description: "Bluetooth devices popup" },
|
||||||
|
"network": { name: "Network (legacy)", description: "Wi-Fi/ethernet status" },
|
||||||
|
"networkPanel": { name: "Network", description: "Wi-Fi list and connect" },
|
||||||
|
"audio": { name: "Volume (legacy)", description: "Speaker icon, scroll for volume" },
|
||||||
|
"audioPanel": { name: "Volume", description: "Volume slider, output picker, mixer" },
|
||||||
|
"microphone": { name: "Microphone", description: "Mic input state" },
|
||||||
|
"nightLight": { name: "Night light", description: "hyprsunset toggle" },
|
||||||
|
"brightness": { name: "Brightness", description: "Screen brightness slider", settingsForm: "brightnessSettings" },
|
||||||
|
"powerProfile": { name: "Power profile", description: "power-profiles-daemon selector" },
|
||||||
|
"battery": { name: "Battery", description: "Battery percent and ETA" },
|
||||||
|
"cpu": { name: "CPU (legacy)", description: "btop launcher" },
|
||||||
|
"systemStats": { name: "System stats", description: "Inline CPU + RAM graphs" },
|
||||||
|
"controlCenter": { name: "Quick settings", description: "Volume/brightness/DND/etc in one popup" },
|
||||||
|
"powerMenu": { name: "Power menu", description: "Lock/suspend/reboot/shutdown" },
|
||||||
|
"keyboardLayout": { name: "Keyboard layout", description: "Current xkb layout, click cycles" },
|
||||||
|
"lockKeys": { name: "Lock keys", description: "Caps/Num/Scroll lock indicators" },
|
||||||
|
"spacer": { name: "Spacer", description: "Configurable blank space", settingsForm: "spacerSettings" }
|
||||||
|
})
|
||||||
|
|
||||||
|
function widgetName(id) {
|
||||||
|
return catalog[id] ? catalog[id].name : id
|
||||||
|
}
|
||||||
|
|
||||||
|
function widgetDescription(id) {
|
||||||
|
return catalog[id] ? (catalog[id].description || "") : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function widgetHasSettings(id) {
|
||||||
|
return !!(catalog[id] && catalog[id].settingsForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
function availableToAdd(section) {
|
||||||
|
var existingByOther = {}
|
||||||
|
var sections = ["left", "center", "right"]
|
||||||
|
for (var s = 0; s < sections.length; s++) {
|
||||||
|
if (sections[s] === section) continue
|
||||||
|
var list = draft.layout[sections[s]] || []
|
||||||
|
for (var i = 0; i < list.length; i++) existingByOther[list[i].id] = true
|
||||||
|
}
|
||||||
|
var existingHere = {}
|
||||||
|
var here = draft.layout[section] || []
|
||||||
|
for (var j = 0; j < here.length; j++) existingHere[here[j].id] = true
|
||||||
|
|
||||||
|
var ids = Object.keys(catalog).sort(function(a, b) {
|
||||||
|
return widgetName(a).localeCompare(widgetName(b))
|
||||||
|
})
|
||||||
|
|
||||||
|
var result = []
|
||||||
|
for (var k = 0; k < ids.length; k++) {
|
||||||
|
var id = ids[k]
|
||||||
|
// Allow multiple instances of `spacer` only.
|
||||||
|
var allowMultiple = id === "spacer"
|
||||||
|
if (!allowMultiple && existingHere[id]) continue
|
||||||
|
result.push({ id: id, name: widgetName(id), description: widgetDescription(id), elsewhere: !!existingByOther[id] })
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: defaultsFile
|
||||||
|
path: root.defaultsPath
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.loadConfig()
|
||||||
|
onFileChanged: { reload(); root.loadConfig() }
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: userFile
|
||||||
|
path: root.userConfigPath
|
||||||
|
watchChanges: true
|
||||||
|
atomicWrites: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.loadConfig()
|
||||||
|
onFileChanged: { reload(); root.loadConfig() }
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: root.home + "/.config/omarchy/current/theme/colors.toml"
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.loadTheme(text())
|
||||||
|
onFileChanged: { reload(); root.loadTheme(text()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingWindow {
|
||||||
|
id: window
|
||||||
|
title: "Omarchy bar settings"
|
||||||
|
color: root.background
|
||||||
|
implicitWidth: 720
|
||||||
|
implicitHeight: 720
|
||||||
|
minimumSize: Qt.size(560, 500)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.background
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 32
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Bar settings"
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.bold: true
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 8
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
ActionPill {
|
||||||
|
text: "Reset to defaults"
|
||||||
|
foreground: root.urgent
|
||||||
|
onClicked: root.resetToDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPill {
|
||||||
|
text: root.dirty ? "Save" : "Saved"
|
||||||
|
foreground: root.dirty ? root.accent : Qt.darker(root.foreground, 1.5)
|
||||||
|
bordered: root.dirty
|
||||||
|
onClicked: if (root.dirty) root.saveConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
OptionDropdown {
|
||||||
|
label: "Position"
|
||||||
|
value: root.draft.position
|
||||||
|
options: ["top", "right", "bottom", "left"]
|
||||||
|
onChanged: function(v) {
|
||||||
|
root.draft.position = v
|
||||||
|
root.markDirty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionDropdown {
|
||||||
|
label: "Center anchor"
|
||||||
|
value: root.draft.centerAnchor
|
||||||
|
options: {
|
||||||
|
var list = ["(none)"]
|
||||||
|
var entries = root.draft.layout.center || []
|
||||||
|
for (var i = 0; i < entries.length; i++) list.push(entries[i].id)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
onChanged: function(v) {
|
||||||
|
root.draft.centerAnchor = v === "(none)" ? "" : v
|
||||||
|
root.markDirty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: bodyScroll
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: bodyColumn.implicitHeight
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: bodyColumn
|
||||||
|
width: bodyScroll.width
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
SectionEditor { sectionKey: "left"; sectionLabel: "Left" }
|
||||||
|
SectionEditor { sectionKey: "center"; sectionLabel: "Center" }
|
||||||
|
SectionEditor { sectionKey: "right"; sectionLabel: "Right" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Components ---------------------------------------------------
|
||||||
|
|
||||||
|
component ActionPill: Rectangle {
|
||||||
|
id: pill
|
||||||
|
property string text: ""
|
||||||
|
property color foreground: root.foreground
|
||||||
|
property bool bordered: true
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
implicitWidth: pillLabel.implicitWidth + 22
|
||||||
|
implicitHeight: 26
|
||||||
|
radius: 4
|
||||||
|
color: pillArea.containsMouse ? Qt.rgba(pill.foreground.r, pill.foreground.g, pill.foreground.b, 0.15) : "transparent"
|
||||||
|
border.color: pill.bordered ? pill.foreground : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: pillLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: pill.text
|
||||||
|
color: pill.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pillArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: pill.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component OptionDropdown: Item {
|
||||||
|
id: dropdown
|
||||||
|
property string label: ""
|
||||||
|
property string value: ""
|
||||||
|
property var options: []
|
||||||
|
signal changed(string value)
|
||||||
|
|
||||||
|
implicitWidth: 240
|
||||||
|
implicitHeight: 48
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: dropdown.label
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: combo
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
model: dropdown.options
|
||||||
|
currentIndex: {
|
||||||
|
for (var i = 0; i < model.length; i++) if (model[i] === dropdown.value) return i
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivated: function(index) {
|
||||||
|
dropdown.changed(model[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: root.background
|
||||||
|
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4)
|
||||||
|
border.width: 1
|
||||||
|
radius: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
leftPadding: 8
|
||||||
|
rightPadding: 24
|
||||||
|
text: combo.displayText
|
||||||
|
color: root.foreground
|
||||||
|
font: combo.font
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component SectionEditor: Column {
|
||||||
|
id: section
|
||||||
|
|
||||||
|
property string sectionKey: ""
|
||||||
|
property string sectionLabel: ""
|
||||||
|
property var entries: (root.draft.layout && root.draft.layout[section.sectionKey]) || []
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onDraftRevisionChanged() { section.entries = (root.draft.layout && root.draft.layout[section.sectionKey]) || [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: section.width
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: section.sectionLabel
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "· " + section.entries.length + (section.entries.length === 1 ? " widget" : " widgets")
|
||||||
|
color: Qt.darker(root.foreground, 1.5)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { width: section.width - 200 - parent.children[0].implicitWidth - parent.children[1].implicitWidth; height: 1 }
|
||||||
|
|
||||||
|
ActionPill {
|
||||||
|
text: "+ Add widget"
|
||||||
|
onClicked: addMenu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: addMenu
|
||||||
|
Repeater {
|
||||||
|
model: root.availableToAdd(section.sectionKey)
|
||||||
|
delegate: MenuItem {
|
||||||
|
required property var modelData
|
||||||
|
text: modelData.name + (modelData.elsewhere ? " (elsewhere)" : "")
|
||||||
|
onTriggered: root.addEntry(section.sectionKey, modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
width: section.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: section.entries
|
||||||
|
delegate: WidgetCard {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
width: section.width
|
||||||
|
sectionKey: section.sectionKey
|
||||||
|
entryIndex: index
|
||||||
|
entry: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: section.entries.length === 0
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: 4
|
||||||
|
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||||
|
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Empty — add a widget"
|
||||||
|
color: Qt.darker(root.foreground, 1.5)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component WidgetCard: Rectangle {
|
||||||
|
id: card
|
||||||
|
property string sectionKey: ""
|
||||||
|
property int entryIndex: -1
|
||||||
|
property var entry: ({})
|
||||||
|
readonly property string entryId: entry && entry.id ? String(entry.id) : ""
|
||||||
|
readonly property string displayName: root.widgetName(entryId)
|
||||||
|
readonly property string description: root.widgetDescription(entryId)
|
||||||
|
readonly property bool hasSettings: root.widgetHasSettings(entryId)
|
||||||
|
|
||||||
|
implicitHeight: 50
|
||||||
|
radius: 4
|
||||||
|
color: cardArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)
|
||||||
|
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionRow
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
glyph: "↑"
|
||||||
|
tooltip: "Move up"
|
||||||
|
onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex - 1)
|
||||||
|
}
|
||||||
|
IconButton {
|
||||||
|
glyph: "↓"
|
||||||
|
tooltip: "Move down"
|
||||||
|
onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex + 1)
|
||||||
|
}
|
||||||
|
IconButton {
|
||||||
|
glyph: "⚙"
|
||||||
|
tooltip: "Settings"
|
||||||
|
visible: card.hasSettings
|
||||||
|
onClicked: settingsLoader.open(card.entry)
|
||||||
|
}
|
||||||
|
IconButton {
|
||||||
|
glyph: "✕"
|
||||||
|
tooltip: "Remove"
|
||||||
|
foreground: root.urgent
|
||||||
|
onClicked: root.removeEntry(card.sectionKey, card.entryIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: actionRow.left
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: card.displayName
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
visible: text !== ""
|
||||||
|
text: card.description
|
||||||
|
color: Qt.darker(root.foreground, 1.5)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 10
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cardArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDialog {
|
||||||
|
id: settingsLoader
|
||||||
|
anchorWindow: window
|
||||||
|
sectionKey: card.sectionKey
|
||||||
|
entryIndex: card.entryIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component IconButton: Rectangle {
|
||||||
|
id: iconButton
|
||||||
|
property string glyph: ""
|
||||||
|
property string tooltip: ""
|
||||||
|
property color foreground: root.foreground
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
implicitWidth: 26
|
||||||
|
implicitHeight: 26
|
||||||
|
radius: 3
|
||||||
|
color: iconArea.containsMouse ? Qt.rgba(iconButton.foreground.r, iconButton.foreground.g, iconButton.foreground.b, 0.18) : "transparent"
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: iconButton.glyph
|
||||||
|
color: iconButton.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: iconArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: iconButton.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component SettingsDialog: Item {
|
||||||
|
id: dialog
|
||||||
|
property var anchorWindow: null
|
||||||
|
property string sectionKey: ""
|
||||||
|
property int entryIndex: -1
|
||||||
|
property var workingEntry: ({})
|
||||||
|
|
||||||
|
function open(entry) {
|
||||||
|
workingEntry = root.cloneJson(entry)
|
||||||
|
win.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit() {
|
||||||
|
root.updateEntry(sectionKey, entryIndex, workingEntry)
|
||||||
|
win.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function discard() {
|
||||||
|
win.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldChanged(key, value) {
|
||||||
|
var copy = root.cloneJson(workingEntry)
|
||||||
|
copy[key] = value
|
||||||
|
workingEntry = copy
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingWindow {
|
||||||
|
id: win
|
||||||
|
title: "Widget settings — " + root.widgetName(dialog.workingEntry.id || "")
|
||||||
|
color: root.background
|
||||||
|
implicitWidth: 380
|
||||||
|
implicitHeight: 320
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.background
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 18
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.widgetName(dialog.workingEntry.id || "")
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.widgetDescription(dialog.workingEntry.id || "")
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: formLoader
|
||||||
|
Layout.fillWidth: true
|
||||||
|
sourceComponent: formComponent(dialog.workingEntry.id || "")
|
||||||
|
onLoaded: {
|
||||||
|
if (item && "entry" in item) item.entry = dialog.workingEntry
|
||||||
|
if (item && "fieldChanged" in item) {
|
||||||
|
item.fieldChanged.connect(function(key, value) { dialog.fieldChanged(key, value) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
spacing: 8
|
||||||
|
ActionPill { text: "Cancel"; bordered: false; onClicked: dialog.discard() }
|
||||||
|
ActionPill { text: "Apply"; onClicked: dialog.commit() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formComponent(id) {
|
||||||
|
var cat = catalog[id]
|
||||||
|
if (!cat || !cat.settingsForm) return null
|
||||||
|
switch (cat.settingsForm) {
|
||||||
|
case "spacerSettings": return spacerSettingsComponent
|
||||||
|
case "calendarSettings": return calendarSettingsComponent
|
||||||
|
case "brightnessSettings": return brightnessSettingsComponent
|
||||||
|
default: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: spacerSettingsComponent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: spacerForm
|
||||||
|
signal fieldChanged(string key, var value)
|
||||||
|
property var entry: ({})
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Size (pixels)"
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBox {
|
||||||
|
from: 0
|
||||||
|
to: 256
|
||||||
|
value: spacerForm.entry.size !== undefined ? spacerForm.entry.size : 12
|
||||||
|
onValueChanged: spacerForm.fieldChanged("size", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: calendarSettingsComponent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: calForm
|
||||||
|
signal fieldChanged(string key, var value)
|
||||||
|
property var entry: ({})
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Horizontal format"
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
text: calForm.entry.format || "dddd HH:mm"
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 12
|
||||||
|
width: parent.width
|
||||||
|
onEditingFinished: calForm.fieldChanged("format", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Alternate format (click to swap)"
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
text: calForm.entry.formatAlt || "dd MMMM 'W'ww yyyy"
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 12
|
||||||
|
width: parent.width
|
||||||
|
onEditingFinished: calForm.fieldChanged("formatAlt", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Vertical format (left/right bars)"
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
text: calForm.entry.verticalFormat || "HH\n—\nmm"
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 12
|
||||||
|
width: parent.width
|
||||||
|
onEditingFinished: calForm.fieldChanged("verticalFormat", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: brightnessSettingsComponent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: brightForm
|
||||||
|
signal fieldChanged(string key, var value)
|
||||||
|
property var entry: ({})
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Scroll step (% per notch)"
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: 11
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
from: 1
|
||||||
|
to: 25
|
||||||
|
value: brightForm.entry.step !== undefined ? brightForm.entry.step : 5
|
||||||
|
onValueChanged: brightForm.fieldChanged("step", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,21 +9,40 @@ This is the Quickshell implementation of the Omarchy status bar.
|
|||||||
- User overrides live in `~/.config/omarchy/bar.json` and are merged over defaults at runtime.
|
- User overrides live in `~/.config/omarchy/bar.json` and are merged over defaults at runtime.
|
||||||
- `omarchy-style-bar-position` updates only the user override file.
|
- `omarchy-style-bar-position` updates only the user override file.
|
||||||
|
|
||||||
Example user override:
|
## Customizing
|
||||||
|
|
||||||
|
The bar reads `~/.local/share/omarchy/default/quickshell/bar/bar-defaults.json`, then deep-merges `~/.config/omarchy/bar.json` on top of it. Each `layout.{left,center,right}` entry is an object: at minimum `{ "id": "<widget>" }`, plus any inline settings the widget reads.
|
||||||
|
|
||||||
|
Launch the visual editor with `omarchy launch bar-settings` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand.
|
||||||
|
|
||||||
|
Example `bar.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"position": "top",
|
"position": "top",
|
||||||
|
"centerAnchor": "calendar",
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": ["omarchy", "workspacesPro"],
|
"left": [
|
||||||
"center": ["media", "calendar", "weatherFlyout"],
|
{ "id": "omarchy" },
|
||||||
"right": ["systemStats", "notificationCenter", "bluetoothPanel", "networkPanel", "audioPanel", "brightness", "powerProfile", "battery", "powerMenu"]
|
{ "id": "spacer", "size": 12 },
|
||||||
},
|
{ "id": "workspacesPro" }
|
||||||
"centerAnchor": "calendar"
|
],
|
||||||
|
"center": [
|
||||||
|
{ "id": "media" },
|
||||||
|
{ "id": "calendar", "format": "HH:mm" }
|
||||||
|
],
|
||||||
|
"right": [
|
||||||
|
{ "id": "systemStats" },
|
||||||
|
{ "id": "audioPanel" },
|
||||||
|
{ "id": "battery" },
|
||||||
|
{ "id": "controlCenter" },
|
||||||
|
{ "id": "powerMenu" }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`centerAnchor` pins one center module to the exact horizontal/vertical center and flanks others around it.
|
`centerAnchor` pins one center module to the exact horizontal/vertical center and flanks others around it. Set to an empty string to disable anchoring (the center list is centered as a group).
|
||||||
|
|
||||||
## Module catalogue
|
## Module catalogue
|
||||||
|
|
||||||
@@ -58,23 +77,18 @@ All widgets work in `top`, `bottom`, `left`, and `right` positions. Popups ancho
|
|||||||
|
|
||||||
## Custom user modules
|
## Custom user modules
|
||||||
|
|
||||||
Add a module name to a layout list, then define it under `modules` in `~/.config/omarchy/bar.json`.
|
The schema accepts arbitrary module ids that you provide. Set `type` to `command` for shell-driven output or `qml` for a custom QML widget.
|
||||||
|
|
||||||
For simple text/JSON output, use a command module:
|
Command module:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"layout": {
|
"layout": {
|
||||||
"right": ["tray", "vpn", "audioPanel", "cpu"]
|
"right": [
|
||||||
},
|
{ "id": "tray" },
|
||||||
"modules": {
|
{ "id": "vpn", "type": "command", "exec": "~/.config/omarchy/bar/scripts/vpn-status", "interval": 5, "tooltip": "VPN", "onClick": "nm-connection-editor" },
|
||||||
"vpn": {
|
{ "id": "audioPanel" }
|
||||||
"type": "command",
|
]
|
||||||
"exec": "~/.config/omarchy/bar/scripts/vpn-status",
|
|
||||||
"interval": 5,
|
|
||||||
"tooltip": "VPN",
|
|
||||||
"onClick": "nm-connection-editor"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -85,17 +99,15 @@ The command may print plain text or Waybar-style JSON, for example:
|
|||||||
{"text":"","tooltip":"Work VPN","class":"active"}
|
{"text":"","tooltip":"Work VPN","class":"active"}
|
||||||
```
|
```
|
||||||
|
|
||||||
For advanced custom UI, use QML:
|
QML module:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"layout": {
|
"layout": {
|
||||||
"right": ["gpu", "audioPanel", "cpu"]
|
"right": [
|
||||||
},
|
{ "id": "gpu", "type": "qml" },
|
||||||
"modules": {
|
{ "id": "audioPanel" }
|
||||||
"gpu": {
|
]
|
||||||
"type": "qml"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,20 +3,34 @@
|
|||||||
"fontFamily": "JetBrainsMono Nerd Font",
|
"fontFamily": "JetBrainsMono Nerd Font",
|
||||||
"centerAnchor": "calendar",
|
"centerAnchor": "calendar",
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": ["omarchy", "workspacesPro", "activeWindow"],
|
"left": [
|
||||||
"center": ["media", "calendar", "weatherFlyout", "update", "voxtype", "screenRecording", "idle", "notifications"],
|
{ "id": "omarchy" },
|
||||||
"right": ["tray", "systemStats", "microphone", "bluetoothPanel", "networkPanel", "audioPanel", "nightLight", "brightness", "powerProfile", "battery", "controlCenter", "powerMenu"]
|
{ "id": "workspacesPro" },
|
||||||
},
|
{ "id": "activeWindow" }
|
||||||
"modules": {
|
],
|
||||||
"calendar": {
|
"center": [
|
||||||
"format": "dddd HH:mm",
|
{ "id": "media" },
|
||||||
"formatAlt": "dd MMMM 'W'ww yyyy",
|
{ "id": "calendar", "format": "dddd HH:mm", "formatAlt": "dd MMMM 'W'ww yyyy", "verticalFormat": "HH\n—\nmm" },
|
||||||
"verticalFormat": "HH\n—\nmm"
|
{ "id": "weatherFlyout" },
|
||||||
},
|
{ "id": "update" },
|
||||||
"clock": {
|
{ "id": "voxtype" },
|
||||||
"format": "dddd HH:mm",
|
{ "id": "screenRecording" },
|
||||||
"formatAlt": "dd MMMM 'W'ww yyyy",
|
{ "id": "idle" },
|
||||||
"verticalFormat": "HH\n—\nmm"
|
{ "id": "notifications" }
|
||||||
}
|
],
|
||||||
|
"right": [
|
||||||
|
{ "id": "tray" },
|
||||||
|
{ "id": "systemStats" },
|
||||||
|
{ "id": "microphone" },
|
||||||
|
{ "id": "bluetoothPanel" },
|
||||||
|
{ "id": "networkPanel" },
|
||||||
|
{ "id": "audioPanel" },
|
||||||
|
{ "id": "nightLight" },
|
||||||
|
{ "id": "brightness" },
|
||||||
|
{ "id": "powerProfile" },
|
||||||
|
{ "id": "battery" },
|
||||||
|
{ "id": "controlCenter" },
|
||||||
|
{ "id": "powerMenu" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,22 +21,21 @@ ShellRoot {
|
|||||||
fontFamily: "JetBrainsMono Nerd Font",
|
fontFamily: "JetBrainsMono Nerd Font",
|
||||||
centerAnchor: "clock",
|
centerAnchor: "clock",
|
||||||
layout: {
|
layout: {
|
||||||
left: ["omarchy", "workspaces"],
|
left: [{ id: "omarchy" }, { id: "workspaces" }],
|
||||||
center: ["clock", "weather", "update", "voxtype", "screenRecording", "idle", "notifications"],
|
center: [
|
||||||
right: ["tray", "bluetooth", "network", "audio", "cpu", "battery"]
|
{ id: "clock", format: "dddd HH:mm", formatAlt: "dd MMMM 'W'ww yyyy", verticalFormat: "HH\n—\nmm" },
|
||||||
},
|
{ id: "weather" }, { id: "update" }, { id: "voxtype" },
|
||||||
modules: {
|
{ id: "screenRecording" }, { id: "idle" }, { id: "notifications" }
|
||||||
clock: {
|
],
|
||||||
format: "dddd HH:mm",
|
right: [
|
||||||
formatAlt: "dd MMMM 'W'ww yyyy",
|
{ id: "tray" }, { id: "bluetooth" }, { id: "network" },
|
||||||
verticalFormat: "HH\n—\nmm"
|
{ id: "audio" }, { id: "cpu" }, { id: "battery" }
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
property var defaultBarConfig: builtinBarConfig
|
property var defaultBarConfig: builtinBarConfig
|
||||||
property var userBarConfig: ({})
|
property var userBarConfig: ({})
|
||||||
property var layoutConfig: builtinBarConfig.layout
|
property var layoutConfig: builtinBarConfig.layout
|
||||||
property var moduleConfig: builtinBarConfig.modules
|
|
||||||
property string centerAnchor: "clock"
|
property string centerAnchor: "clock"
|
||||||
property int barConfigSerial: 0
|
property int barConfigSerial: 0
|
||||||
property string position: "top"
|
property string position: "top"
|
||||||
@@ -145,14 +144,38 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeLayoutEntry(entry) {
|
||||||
|
if (typeof entry === "string") return { id: entry }
|
||||||
|
if (isPlainObject(entry) && entry.id) return entry
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayoutSection(list) {
|
||||||
|
if (!Array.isArray(list)) return []
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var normalized = normalizeLayoutEntry(list[i])
|
||||||
|
if (normalized) result.push(normalized)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLayout(layout) {
|
||||||
|
if (!isPlainObject(layout)) layout = builtinBarConfig.layout
|
||||||
|
return {
|
||||||
|
left: normalizeLayoutSection(layout.left),
|
||||||
|
center: normalizeLayoutSection(layout.center),
|
||||||
|
right: normalizeLayoutSection(layout.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyBarConfig() {
|
function applyBarConfig() {
|
||||||
var config = mergeConfig(defaultBarConfig, userBarConfig)
|
var config = mergeConfig(defaultBarConfig, userBarConfig)
|
||||||
|
|
||||||
position = normalizePosition(config.position)
|
position = normalizePosition(config.position)
|
||||||
fontFamily = String(config.fontFamily || "JetBrainsMono Nerd Font")
|
fontFamily = String(config.fontFamily || "JetBrainsMono Nerd Font")
|
||||||
centerAnchor = String(config.centerAnchor || "clock")
|
centerAnchor = String(config.centerAnchor || "clock")
|
||||||
layoutConfig = isPlainObject(config.layout) ? config.layout : builtinBarConfig.layout
|
layoutConfig = normalizeLayout(config.layout)
|
||||||
moduleConfig = isPlainObject(config.modules) ? config.modules : {}
|
|
||||||
barConfigSerial++
|
barConfigSerial++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,42 +189,53 @@ ShellRoot {
|
|||||||
applyBarConfig()
|
applyBarConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
function layoutModules(region) {
|
function layoutEntries(region) {
|
||||||
var serial = barConfigSerial
|
var serial = barConfigSerial
|
||||||
var modules = layoutConfig ? layoutConfig[region] : null
|
var entries = layoutConfig ? layoutConfig[region] : null
|
||||||
return Array.isArray(modules) ? modules : []
|
return Array.isArray(entries) ? entries : []
|
||||||
}
|
}
|
||||||
|
|
||||||
function moduleSettings(name) {
|
function entrySettings(entry) {
|
||||||
var serial = barConfigSerial
|
if (!isPlainObject(entry)) return {}
|
||||||
var settings = moduleConfig ? moduleConfig[String(name)] : null
|
var copy = {}
|
||||||
return isPlainObject(settings) ? settings : {}
|
for (var key in entry) {
|
||||||
|
if (key === "id") continue
|
||||||
|
copy[key] = entry[key]
|
||||||
|
}
|
||||||
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
function moduleString(name, key, fallback) {
|
function entryId(entry) {
|
||||||
var value = moduleSettings(name)[key]
|
if (typeof entry === "string") return entry
|
||||||
|
if (isPlainObject(entry) && entry.id) return String(entry.id)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function moduleString(entry, key, fallback) {
|
||||||
|
var settings = entrySettings(entry)
|
||||||
|
var value = settings[key]
|
||||||
return value === undefined || value === null ? fallback : String(value)
|
return value === undefined || value === null ? fallback : String(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moduleIndex(modules, name) {
|
function entryIndex(entries, name) {
|
||||||
if (!Array.isArray(modules)) return -1
|
if (!Array.isArray(entries)) return -1
|
||||||
|
|
||||||
for (var i = 0; i < modules.length; i++) {
|
for (var i = 0; i < entries.length; i++) {
|
||||||
if (String(modules[i]) === name)
|
if (entryId(entries[i]) === name)
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
function modulesBefore(modules, name) {
|
function entriesBefore(entries, name) {
|
||||||
var index = moduleIndex(modules, name)
|
var index = entryIndex(entries, name)
|
||||||
return index <= 0 ? [] : modules.slice(0, index)
|
return index <= 0 ? [] : entries.slice(0, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
function modulesAfter(modules, name) {
|
function entriesAfter(entries, name) {
|
||||||
var index = moduleIndex(modules, name)
|
var index = entryIndex(entries, name)
|
||||||
return index === -1 ? [] : modules.slice(index + 1)
|
return index === -1 ? [] : entries.slice(index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function builtinModuleComponent(name) {
|
function builtinModuleComponent(name) {
|
||||||
@@ -241,8 +275,8 @@ ShellRoot {
|
|||||||
return value !== "" && value.indexOf("..") === -1 && value[0] !== "/"
|
return value !== "" && value.indexOf("..") === -1 && value[0] !== "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
function customModuleType(name) {
|
function customModuleType(entry) {
|
||||||
var settings = moduleSettings(name)
|
var settings = entrySettings(entry)
|
||||||
var type = String(settings.type || "")
|
var type = String(settings.type || "")
|
||||||
if (type) return type
|
if (type) return type
|
||||||
if (settings.exec) return "command"
|
if (settings.exec) return "command"
|
||||||
@@ -250,8 +284,9 @@ ShellRoot {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function customModuleSource(name) {
|
function customModuleSource(entry) {
|
||||||
var settings = moduleSettings(name)
|
var settings = entrySettings(entry)
|
||||||
|
var name = entryId(entry)
|
||||||
var source = settings.source ? expandPath(settings.source) : ""
|
var source = settings.source ? expandPath(settings.source) : ""
|
||||||
if (!source && customModuleSafeName(name))
|
if (!source && customModuleSafeName(name))
|
||||||
source = omarchyConfigDir + "/bar/modules/" + String(name) + ".qml"
|
source = omarchyConfigDir + "/bar/modules/" + String(name) + ".qml"
|
||||||
@@ -601,14 +636,26 @@ ShellRoot {
|
|||||||
root.run("hyprctl dispatch " + shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
root.run("hyprctl dispatch " + shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clockEntry() {
|
||||||
|
var serial = barConfigSerial
|
||||||
|
var sections = ["left", "center", "right"]
|
||||||
|
for (var i = 0; i < sections.length; i++) {
|
||||||
|
var entries = layoutEntries(sections[i])
|
||||||
|
var idx = entryIndex(entries, "clock")
|
||||||
|
if (idx !== -1) return entries[idx]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function clockText() {
|
function clockText() {
|
||||||
|
var entry = clockEntry()
|
||||||
if (clockAlt)
|
if (clockAlt)
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString("clock", "formatAlt", "dd MMMM 'W'ww yyyy"))
|
return Qt.formatDateTime(systemClock.date, moduleString(entry, "formatAlt", "dd MMMM 'W'ww yyyy"))
|
||||||
|
|
||||||
if (vertical)
|
if (vertical)
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString("clock", "verticalFormat", "HH\n—\nmm"))
|
return Qt.formatDateTime(systemClock.date, moduleString(entry, "verticalFormat", "HH\n—\nmm"))
|
||||||
|
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString("clock", "format", "dddd HH:mm"))
|
return Qt.formatDateTime(systemClock.date, moduleString(entry, "format", "dddd HH:mm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemClock {
|
SystemClock {
|
||||||
@@ -947,19 +994,26 @@ ShellRoot {
|
|||||||
Component { id: cpuModuleComponent; CpuModule {} }
|
Component { id: cpuModuleComponent; CpuModule {} }
|
||||||
Component { id: batteryModuleComponent; BatteryModule {} }
|
Component { id: batteryModuleComponent; BatteryModule {} }
|
||||||
|
|
||||||
|
function findCenterAnchorEntry() {
|
||||||
|
var entries = root.layoutEntries("center")
|
||||||
|
var idx = root.entryIndex(entries, root.centerAnchor)
|
||||||
|
return idx === -1 ? null : entries[idx]
|
||||||
|
}
|
||||||
|
|
||||||
component LeftModules: ModuleList {
|
component LeftModules: ModuleList {
|
||||||
modules: root.layoutModules("left")
|
entries: root.layoutEntries("left")
|
||||||
}
|
}
|
||||||
|
|
||||||
component RightModules: ModuleList {
|
component RightModules: ModuleList {
|
||||||
modules: root.layoutModules("right")
|
entries: root.layoutEntries("right")
|
||||||
}
|
}
|
||||||
|
|
||||||
component CenterModules: Item {
|
component CenterModules: Item {
|
||||||
id: centerRoot
|
id: centerRoot
|
||||||
|
|
||||||
property var modules: root.layoutModules("center")
|
property var entries: root.layoutEntries("center")
|
||||||
readonly property bool hasAnchor: root.moduleIndex(modules, root.centerAnchor) !== -1
|
readonly property bool hasAnchor: root.entryIndex(entries, root.centerAnchor) !== -1
|
||||||
|
readonly property var anchorEntry: root.findCenterAnchorEntry()
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -974,13 +1028,13 @@ ShellRoot {
|
|||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: !centerRoot.hasAnchor
|
visible: !centerRoot.hasAnchor
|
||||||
modules: centerRoot.modules
|
entries: centerRoot.entries
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
modules: root.modulesBefore(centerRoot.modules, root.centerAnchor)
|
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||||
anchors.right: centerAnchorModule.left
|
anchors.right: centerAnchorModule.left
|
||||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -988,13 +1042,13 @@ ShellRoot {
|
|||||||
ModuleSlot {
|
ModuleSlot {
|
||||||
id: centerAnchorModule
|
id: centerAnchorModule
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
moduleName: root.centerAnchor
|
entry: centerRoot.anchorEntry
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
modules: root.modulesAfter(centerRoot.modules, root.centerAnchor)
|
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||||
anchors.left: centerAnchorModule.right
|
anchors.left: centerAnchorModule.right
|
||||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -1009,13 +1063,13 @@ ShellRoot {
|
|||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: !centerRoot.hasAnchor
|
visible: !centerRoot.hasAnchor
|
||||||
modules: centerRoot.modules
|
entries: centerRoot.entries
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
modules: root.modulesBefore(centerRoot.modules, root.centerAnchor)
|
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||||
anchors.bottom: centerAnchorModule.top
|
anchors.bottom: centerAnchorModule.top
|
||||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||||
}
|
}
|
||||||
@@ -1023,13 +1077,13 @@ ShellRoot {
|
|||||||
ModuleSlot {
|
ModuleSlot {
|
||||||
id: centerAnchorModule
|
id: centerAnchorModule
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
moduleName: root.centerAnchor
|
entry: centerRoot.anchorEntry
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
visible: centerRoot.hasAnchor
|
visible: centerRoot.hasAnchor
|
||||||
modules: root.modulesAfter(centerRoot.modules, root.centerAnchor)
|
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||||
anchors.top: centerAnchorModule.bottom
|
anchors.top: centerAnchorModule.bottom
|
||||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||||
}
|
}
|
||||||
@@ -1040,9 +1094,9 @@ ShellRoot {
|
|||||||
component ModuleList: Loader {
|
component ModuleList: Loader {
|
||||||
id: moduleListRoot
|
id: moduleListRoot
|
||||||
|
|
||||||
property var modules: []
|
property var entries: []
|
||||||
|
|
||||||
visible: modules.length > 0
|
visible: entries.length > 0
|
||||||
sourceComponent: root.vertical ? verticalModuleList : horizontalModuleList
|
sourceComponent: root.vertical ? verticalModuleList : horizontalModuleList
|
||||||
width: item ? item.implicitWidth : 0
|
width: item ? item.implicitWidth : 0
|
||||||
height: item ? item.implicitHeight : 0
|
height: item ? item.implicitHeight : 0
|
||||||
@@ -1054,11 +1108,11 @@ ShellRoot {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: moduleListRoot.modules
|
model: moduleListRoot.entries
|
||||||
|
|
||||||
ModuleSlot {
|
ModuleSlot {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
moduleName: String(modelData)
|
entry: modelData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,11 +1125,11 @@ ShellRoot {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: moduleListRoot.modules
|
model: moduleListRoot.entries
|
||||||
|
|
||||||
ModuleSlot {
|
ModuleSlot {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
moduleName: String(modelData)
|
entry: modelData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1085,8 +1139,10 @@ ShellRoot {
|
|||||||
component ModuleSlot: Item {
|
component ModuleSlot: Item {
|
||||||
id: slot
|
id: slot
|
||||||
|
|
||||||
required property string moduleName
|
required property var entry
|
||||||
readonly property string customType: root.customModuleType(moduleName)
|
readonly property string moduleName: root.entryId(entry)
|
||||||
|
readonly property var moduleSettings: root.entrySettings(entry)
|
||||||
|
readonly property string customType: root.customModuleType(entry)
|
||||||
readonly property var builtinComponent: customType ? null : root.builtinModuleComponent(moduleName)
|
readonly property var builtinComponent: customType ? null : root.builtinModuleComponent(moduleName)
|
||||||
readonly property string firstPartySource: customType || builtinComponent ? "" : root.firstPartyWidgetSource(moduleName)
|
readonly property string firstPartySource: customType || builtinComponent ? "" : root.firstPartyWidgetSource(moduleName)
|
||||||
readonly property bool qmlCustom: customType === "qml"
|
readonly property bool qmlCustom: customType === "qml"
|
||||||
@@ -1109,35 +1165,33 @@ ShellRoot {
|
|||||||
Loader {
|
Loader {
|
||||||
id: qmlLoader
|
id: qmlLoader
|
||||||
active: slot.qmlCustom || slot.firstParty
|
active: slot.qmlCustom || slot.firstParty
|
||||||
source: slot.qmlCustom ? root.customModuleSource(slot.moduleName) : (slot.firstParty ? slot.firstPartySource : "")
|
source: slot.qmlCustom ? root.customModuleSource(slot.entry) : (slot.firstParty ? slot.firstPartySource : "")
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onLoaded: slot.injectProps()
|
onLoaded: slot.injectProps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onModuleSettingsChanged: injectProps()
|
||||||
|
|
||||||
function injectProps() {
|
function injectProps() {
|
||||||
var target = qmlLoader.item
|
var target = qmlLoader.item
|
||||||
if (!target) return
|
if (!target) return
|
||||||
if ("bar" in target) target.bar = root
|
if ("bar" in target) target.bar = root
|
||||||
if ("moduleName" in target) target.moduleName = moduleName
|
if ("moduleName" in target) target.moduleName = moduleName
|
||||||
if ("settings" in target) target.settings = root.moduleSettings(moduleName)
|
if ("settings" in target) target.settings = moduleSettings
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
function onBarConfigSerialChanged() { slot.injectProps() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: customCommandModuleComponent
|
id: customCommandModuleComponent
|
||||||
CustomCommandModule { moduleName: slot.moduleName }
|
CustomCommandModule { entry: slot.entry }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component CustomCommandModule: ModuleButton {
|
component CustomCommandModule: ModuleButton {
|
||||||
id: customRoot
|
id: customRoot
|
||||||
|
|
||||||
required property string moduleName
|
required property var entry
|
||||||
property var settings: root.moduleSettings(moduleName)
|
readonly property string moduleName: root.entryId(entry)
|
||||||
|
readonly property var settings: root.entrySettings(entry)
|
||||||
property string outputText: ""
|
property string outputText: ""
|
||||||
property string outputTooltip: ""
|
property string outputTooltip: ""
|
||||||
property bool outputActive: false
|
property bool outputActive: false
|
||||||
|
|||||||
@@ -322,6 +322,16 @@ Item {
|
|||||||
color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
|
color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Common.PillButton {
|
||||||
|
width: parent.width
|
||||||
|
iconText: ""
|
||||||
|
text: "Customize bar…"
|
||||||
|
foreground: root.bar.foreground
|
||||||
|
horizontalPadding: 10
|
||||||
|
verticalPadding: 8
|
||||||
|
onClicked: { root.run("omarchy-launch-bar-settings"); root.popupOpen = false }
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|||||||
Reference in New Issue
Block a user