Replace bar settings with inline config panel
This commit is contained in:
+100
-19
@@ -12,8 +12,7 @@ Item {
|
||||
|
||||
// The omarchy-shell host injects omarchyPath from OMARCHY_PATH.
|
||||
required property string omarchyPath
|
||||
// Injected by the host shell. Shared with the bar settings panel so both
|
||||
// see the same widget catalogue.
|
||||
// Injected by the host shell so bar slots can resolve enabled widgets.
|
||||
required property var barWidgetRegistry
|
||||
// Injected by the host shell every time shell.json is reloaded. Holds the
|
||||
// `bar:` subtree: position, centerAnchor, layout. The host owns file IO;
|
||||
@@ -69,6 +68,7 @@ Item {
|
||||
property var activePopout: null
|
||||
property var barDragTarget: null
|
||||
property bool barDragAfter: false
|
||||
property var configControls: []
|
||||
property var clickTargets: []
|
||||
property var debugModuleSlots: []
|
||||
|
||||
@@ -96,6 +96,18 @@ Item {
|
||||
debugModuleSlots = next
|
||||
}
|
||||
|
||||
function registerConfigControl(control) {
|
||||
if (!control || configControls.indexOf(control) !== -1) return
|
||||
var next = configControls.slice()
|
||||
next.push(control)
|
||||
configControls = next
|
||||
}
|
||||
|
||||
function unregisterConfigControl(control) {
|
||||
var next = configControls.filter(function(item) { return item !== control })
|
||||
configControls = next
|
||||
}
|
||||
|
||||
function debugBarGeometry() {
|
||||
var out = []
|
||||
for (var i = 0; i < debugModuleSlots.length; i++) {
|
||||
@@ -164,7 +176,7 @@ Item {
|
||||
}
|
||||
|
||||
// Apply tray-pinning on top of the shared layout normalization so the
|
||||
// bar host and the bar settings panel can't drift on entry shape.
|
||||
// bar host and scriptable config helpers can't drift on entry shape.
|
||||
function normalizeLayout(layout) {
|
||||
var normalized = Util.normalizeLayout(Util.isPlainObject(layout) ? layout : fallbackBarConfig.layout)
|
||||
return {
|
||||
@@ -305,12 +317,14 @@ Item {
|
||||
launcher.startDetached()
|
||||
}
|
||||
|
||||
function openBarSettings() {
|
||||
if (root.shell && typeof root.shell.summon === "function") {
|
||||
root.shell.summon("omarchy.settings", "{}")
|
||||
} else {
|
||||
root.run("omarchy-launch-bar-settings")
|
||||
function openConfigPanel() {
|
||||
for (var i = 0; i < configControls.length; i++) {
|
||||
var control = configControls[i]
|
||||
if (!control || control.visible !== true || typeof control.openPanel !== "function") continue
|
||||
control.openPanel()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function toggleTransparency() {
|
||||
@@ -821,7 +835,7 @@ Item {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||
region: "center"
|
||||
anchors.right: centerAnchorModule.left
|
||||
anchors.right: centerConfigControl.visible ? centerConfigControl.left : centerAnchorModule.left
|
||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||
}
|
||||
|
||||
@@ -833,6 +847,15 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BarConfigControl {
|
||||
id: centerConfigControl
|
||||
|
||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||
clockHovered: centerAnchorModule.hovered
|
||||
anchors.right: centerAnchorModule.left
|
||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||
}
|
||||
|
||||
ModuleList {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||
@@ -862,7 +885,7 @@ Item {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||
region: "center"
|
||||
anchors.bottom: centerAnchorModule.top
|
||||
anchors.bottom: centerConfigControl.visible ? centerConfigControl.top : centerAnchorModule.top
|
||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||
}
|
||||
|
||||
@@ -874,6 +897,15 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BarConfigControl {
|
||||
id: centerConfigControl
|
||||
|
||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||
clockHovered: centerAnchorModule.hovered
|
||||
anchors.bottom: centerAnchorModule.top
|
||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||
}
|
||||
|
||||
ModuleList {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||
@@ -886,23 +918,69 @@ Item {
|
||||
}
|
||||
|
||||
component CenterGestureArea: MouseArea {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
root.openBarSettings()
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onDoubleClicked: function(mouse) {
|
||||
if (mouse.button !== Qt.RightButton) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.toggleTransparency()
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component BarConfigControl: Item {
|
||||
id: configControl
|
||||
|
||||
property bool clockHovered: false
|
||||
|
||||
readonly property bool panelOpen: configPanel.opened
|
||||
readonly property bool revealed: visible && (clockHovered || controlHover.hovered || panelOpen)
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
opacity: revealed ? 1.0 : 0
|
||||
z: 500
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 120; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
HoverHandler { id: controlHover }
|
||||
|
||||
Component.onCompleted: root.registerConfigControl(configControl)
|
||||
Component.onDestruction: root.unregisterConfigControl(configControl)
|
||||
|
||||
function openPanel() {
|
||||
configPanel.open()
|
||||
}
|
||||
|
||||
WidgetButton {
|
||||
id: button
|
||||
|
||||
anchors.fill: parent
|
||||
bar: root
|
||||
text: ""
|
||||
keepSpace: true
|
||||
concealed: !configControl.revealed
|
||||
interactive: configControl.revealed
|
||||
horizontalMargin: 6.5
|
||||
verticalPadding: 6
|
||||
tooltipText: "Bar config"
|
||||
onPressed: function(b) {
|
||||
if (b === Qt.LeftButton) configPanel.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
BarConfigPanel {
|
||||
id: configPanel
|
||||
|
||||
bar: root
|
||||
anchorItem: button
|
||||
}
|
||||
}
|
||||
|
||||
component ModuleList: Loader {
|
||||
id: moduleListRoot
|
||||
|
||||
@@ -976,6 +1054,7 @@ Item {
|
||||
if (qmlCustom) return qmlLoader.item
|
||||
return componentLoader.item
|
||||
}
|
||||
readonly property bool hovered: moduleHover.hovered
|
||||
|
||||
implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
|
||||
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
|
||||
@@ -986,6 +1065,8 @@ Item {
|
||||
Component.onCompleted: root.registerDebugModuleSlot(slot)
|
||||
Component.onDestruction: root.unregisterDebugModuleSlot(slot)
|
||||
|
||||
HoverHandler { id: moduleHover }
|
||||
|
||||
Loader {
|
||||
id: componentLoader
|
||||
active: !slot.qmlCustom && !slot.registered
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Panel {
|
||||
id: root
|
||||
|
||||
moduleName: "omarchy.bar-config"
|
||||
manageIpc: false
|
||||
|
||||
property Item anchorItem: null
|
||||
property string focusSection: "transparency"
|
||||
property int positionIndex: 0
|
||||
property bool cursorActive: false
|
||||
property int phraseIndex: 0
|
||||
|
||||
readonly property string currentPosition: bar ? bar.position : "top"
|
||||
readonly property bool transparent: bar ? bar.requestedTransparent === true : false
|
||||
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
||||
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||
readonly property var heroPhrases: [
|
||||
"Picking Sides",
|
||||
"Choose Transparency",
|
||||
"Edge Decisions",
|
||||
"Bar Exam",
|
||||
"Top Shelf Thinking",
|
||||
"Side Quest Settings"
|
||||
]
|
||||
readonly property string heroPhraseText: heroPhrases[phraseIndex % heroPhrases.length]
|
||||
readonly property var positionOptions: [
|
||||
{ value: "top", label: "Top" },
|
||||
{ value: "bottom", label: "Bottom" },
|
||||
{ value: "left", label: "Left" },
|
||||
{ value: "right", label: "Right" }
|
||||
]
|
||||
|
||||
function normalizePosition(value) {
|
||||
var next = String(value || "")
|
||||
return /^(top|bottom|left|right)$/.test(next) ? next : "top"
|
||||
}
|
||||
|
||||
function positionLabel(value) {
|
||||
var next = normalizePosition(value)
|
||||
return next.charAt(0).toUpperCase() + next.slice(1)
|
||||
}
|
||||
|
||||
function currentPositionIndex() {
|
||||
var current = normalizePosition(currentPosition)
|
||||
for (var i = 0; i < positionOptions.length; i++)
|
||||
if (positionOptions[i].value === current) return i
|
||||
return 0
|
||||
}
|
||||
|
||||
function mutateBarConfig(mutator) {
|
||||
if (!bar || !bar.shell || typeof bar.shell.mutateShellConfig !== "function") return false
|
||||
bar.shell.mutateShellConfig(function(config) {
|
||||
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
||||
mutator(config.bar)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function setTransparency(value) {
|
||||
var next = value === true
|
||||
if (mutateBarConfig(function(barConfig) { barConfig.transparent = next })) return
|
||||
if (bar && typeof bar.setRequestedTransparency === "function") bar.setRequestedTransparency(next)
|
||||
}
|
||||
|
||||
function setPosition(value) {
|
||||
var next = normalizePosition(value)
|
||||
if (mutateBarConfig(function(barConfig) { barConfig.position = next })) return
|
||||
if (bar) bar.position = next
|
||||
}
|
||||
|
||||
function moveCursor(dx, dy) {
|
||||
if (!cursorActive) {
|
||||
cursorActive = true
|
||||
return
|
||||
}
|
||||
|
||||
if (dy !== 0) {
|
||||
focusSection = focusSection === "transparency" ? "position" : "transparency"
|
||||
if (focusSection === "position") positionIndex = currentPositionIndex()
|
||||
return
|
||||
}
|
||||
|
||||
if (dx !== 0 && focusSection === "position") {
|
||||
var count = positionOptions.length
|
||||
positionIndex = (positionIndex + (dx > 0 ? 1 : -1) + count) % count
|
||||
}
|
||||
}
|
||||
|
||||
function activateCursor() {
|
||||
if (focusSection === "transparency") {
|
||||
setTransparency(!transparent)
|
||||
return
|
||||
}
|
||||
|
||||
var option = positionOptions[positionIndex]
|
||||
if (option) setPosition(option.value)
|
||||
}
|
||||
|
||||
onOpenedChanged: {
|
||||
if (!opened) return
|
||||
focusSection = "transparency"
|
||||
positionIndex = currentPositionIndex()
|
||||
cursorActive = false
|
||||
}
|
||||
|
||||
onCurrentPositionChanged: if (!cursorActive || focusSection === "position") positionIndex = currentPositionIndex()
|
||||
|
||||
Timer {
|
||||
id: phraseTimer
|
||||
interval: 2200
|
||||
running: root.opened
|
||||
repeat: true
|
||||
onTriggered: phraseSwap.restart()
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: phraseSwap
|
||||
PropertyAnimation {
|
||||
target: hero; property: "metaOpacity"
|
||||
to: 0.0; duration: 180; easing.type: Easing.OutQuad
|
||||
}
|
||||
ScriptAction {
|
||||
script: root.phraseIndex = (root.phraseIndex + 1) % root.heroPhrases.length
|
||||
}
|
||||
PropertyAnimation {
|
||||
target: hero; property: "metaOpacity"
|
||||
to: 1.0; duration: 260; easing.type: Easing.InQuad
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: heroIconComponent
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.display
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardPanel {
|
||||
id: panel
|
||||
|
||||
anchorItem: root.anchorItem
|
||||
owner: root
|
||||
bar: root.bar
|
||||
open: root.opened && root.anchorItem !== null
|
||||
focusTarget: keyCatcher
|
||||
contentWidth: panel.fittedContentWidth(Style.space(380))
|
||||
contentHeight: panel.fittedContentHeight(contentColumn.implicitHeight)
|
||||
|
||||
PanelKeyCatcher {
|
||||
id: keyCatcher
|
||||
|
||||
anchors.fill: parent
|
||||
onMoveRequested: function(dx, dy) { root.moveCursor(dx, dy) }
|
||||
onActivateRequested: root.activateCursor()
|
||||
onCloseRequested: root.close()
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Style.space(14)
|
||||
|
||||
PanelHero {
|
||||
id: hero
|
||||
|
||||
width: parent.width
|
||||
iconComponent: heroIconComponent
|
||||
title: "Bar"
|
||||
meta: root.heroPhraseText
|
||||
foreground: root.foreground
|
||||
fontFamily: root.fontFamily
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Style.space(8)
|
||||
|
||||
PanelSectionHeader {
|
||||
text: "APPEARANCE"
|
||||
foreground: root.foreground
|
||||
fontFamily: root.fontFamily
|
||||
}
|
||||
|
||||
Toggle {
|
||||
width: parent.width
|
||||
label: "Transparency"
|
||||
description: root.transparent ? "Wallpaper visible" : "Solid background"
|
||||
checked: root.transparent
|
||||
hasCursor: root.cursorActive && root.focusSection === "transparency"
|
||||
foreground: root.foreground
|
||||
accent: Color.accent
|
||||
fontFamily: root.fontFamily
|
||||
onClicked: root.setTransparency(!root.transparent)
|
||||
onHovered: function(h) {
|
||||
if (!h) return
|
||||
root.cursorActive = true
|
||||
root.focusSection = "transparency"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Style.space(8)
|
||||
|
||||
PanelSectionHeader {
|
||||
text: "POSITION"
|
||||
foreground: root.foreground
|
||||
fontFamily: root.fontFamily
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: positionGroup
|
||||
|
||||
width: parent.width
|
||||
options: root.positionOptions
|
||||
value: root.currentPosition
|
||||
foreground: root.foreground
|
||||
background: "transparent"
|
||||
accent: Color.accent
|
||||
fontFamily: root.fontFamily
|
||||
focusable: false
|
||||
cursorIndex: root.cursorActive && root.focusSection === "position" ? root.positionIndex : -1
|
||||
onChanged: function(value) { root.setPosition(value) }
|
||||
onHovered: function(index, hovered) {
|
||||
if (!hovered) return
|
||||
root.cursorActive = true
|
||||
root.focusSection = "position"
|
||||
root.positionIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ the shell for its whole session.
|
||||
|
||||
## Customizing
|
||||
|
||||
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`config/omarchy/shell.json`](../../../config/omarchy/shell.json). Once you customize anything via `omarchy launch bar settings` or by editing shell.json directly, your file is canonical — there is no deep-merge.
|
||||
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`config/omarchy/shell.json`](../../../config/omarchy/shell.json). Once you customize anything via the inline bar config panel, `omarchy plugin bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge.
|
||||
|
||||
Launch the visual editor with `omarchy launch bar settings` / `omarchy plugin bar edit` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand. For scriptable changes, use `omarchy plugin bar list`, `omarchy plugin bar add`, `omarchy plugin bar move`, `omarchy plugin bar remove`, and `omarchy plugin bar set`. You can also right-click empty space to the left or right of the centered clock module to open it; double-left-click the same empty space to toggle bar transparency.
|
||||
Open quick position and transparency controls with `omarchy launch bar settings` / `omarchy plugin bar settings` (or run `omarchy-launch-bar-settings`). You can also hover the centered clock module to reveal the inline bar config button. For scriptable widget changes, use `omarchy plugin bar list`, `omarchy plugin bar add`, `omarchy plugin bar move`, `omarchy plugin bar remove`, and `omarchy plugin bar set`. Double-left-click empty center-bar space to toggle bar transparency.
|
||||
|
||||
Example `shell.json` (bar subtree only shown):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user