Drag the bar to move it, drop the config panel
Position the bar by dragging (or click-and-holding) empty bar space toward a screen edge, with a ghost slab previewing the target edge. With drag for position and double-click for transparency, the bar config panel, its inline gear button, and the omarchy-launch-bar-settings CLI are no longer needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
66cdec78c2
commit
9aa1dcd664
+1
-6
@@ -2,7 +2,7 @@
|
||||
|
||||
# omarchy:summary=Set the active bar option, position, and transparency
|
||||
# omarchy:group=bar
|
||||
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false> | settings
|
||||
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false>
|
||||
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar reset | omarchy bar defaults | omarchy bar position top | omarchy bar transparent true
|
||||
|
||||
set -euo pipefail
|
||||
@@ -20,7 +20,6 @@ Usage: omarchy bar <command> [args...]
|
||||
defaults Restore the default bar and service widgets
|
||||
position <top|bottom|left|right> Bar position
|
||||
transparent <true|false> Bar transparency
|
||||
settings Open the inline bar config panel
|
||||
|
||||
Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'.
|
||||
|
||||
@@ -168,10 +167,6 @@ case "$command" in
|
||||
transparent)
|
||||
cmd_transparent "$@"
|
||||
;;
|
||||
settings)
|
||||
(( $# == 0 )) || fail "settings does not take arguments"
|
||||
exec omarchy-launch-bar-settings
|
||||
;;
|
||||
plugin)
|
||||
exec omarchy-bar-plugin "$@"
|
||||
;;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Open the Omarchy bar config panel
|
||||
|
||||
exec omarchy-shell shell openBarConfig
|
||||
@@ -29,7 +29,6 @@ Options:
|
||||
|
||||
Examples:
|
||||
omarchy-shell shell ping
|
||||
omarchy-shell shell openBarConfig
|
||||
omarchy-shell -q Indicators refresh
|
||||
omarchy-shell shell listPlugins
|
||||
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
|
||||
|
||||
@@ -174,7 +174,7 @@ $OMARCHY_PATH/config/omarchy/shell.json # Canonical defaults
|
||||
The shell hot-reloads `shell.json` on save — no restart needed for layout
|
||||
changes. For more invasive changes (new plugin, packaged update):
|
||||
|
||||
**Commands:** `omarchy-restart-shell`, `omarchy refresh shell`, `omarchy launch bar settings`
|
||||
**Commands:** `omarchy-restart-shell`, `omarchy refresh shell`
|
||||
|
||||
### Terminals
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
"setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
|
||||
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
|
||||
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
|
||||
"setup.config.bar": {"icon":"","label":"Bar","keywords":"quickshell shell config settings widgets position","action":"omarchy-launch-bar-settings"},
|
||||
"setup.config.xcompose": {"icon":"","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
|
||||
|
||||
// Install
|
||||
|
||||
+1
-2
@@ -188,7 +188,6 @@ calls to the running shell. It does not start the shell.
|
||||
|
||||
```
|
||||
omarchy-shell shell ping
|
||||
omarchy-shell shell openBarConfig
|
||||
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
|
||||
omarchy-shell shell listPlugins
|
||||
omarchy-shell shell rescanPlugins
|
||||
@@ -256,7 +255,7 @@ becomes the authoritative file — we do **not** deep-merge defaults back in.
|
||||
like `Clock` and `AudioPanel` forward.
|
||||
5. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff
|
||||
its id appears somewhere in shell.json. For full bar options, that means
|
||||
`bar.id`; for bar widgets, the bar settings UI adds/removes layout entries;
|
||||
`bar.id`; for bar widgets, `omarchy bar plugin` adds/removes layout entries;
|
||||
other plugin kinds are enabled with the shell IPC. First-party non-bar
|
||||
plugins are always enabled.
|
||||
6. **Multiple instances** are allowed when a manifest sets
|
||||
|
||||
+216
-125
@@ -85,7 +85,11 @@ Item {
|
||||
property real barDragScreenY: 0
|
||||
property real barDragOffsetX: 0
|
||||
property real barDragOffsetY: 0
|
||||
property var configControls: []
|
||||
property bool barMoveActive: false
|
||||
property bool barMoveSettling: false
|
||||
property string barMoveCandidate: ""
|
||||
property var barMoveWindow: null
|
||||
property var barMoveScreen: null
|
||||
property var clickTargets: []
|
||||
property var moduleSlots: []
|
||||
|
||||
@@ -113,18 +117,6 @@ Item {
|
||||
moduleSlots = 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 < moduleSlots.length; i++) {
|
||||
@@ -198,10 +190,9 @@ Item {
|
||||
barDragOffsetY = 0
|
||||
}
|
||||
|
||||
function barDragScreenPoint(scenePoint) {
|
||||
function windowScreenPoint(scenePoint, window) {
|
||||
var x = scenePoint ? scenePoint.x : 0
|
||||
var y = scenePoint ? scenePoint.y : 0
|
||||
var window = barDragWindow
|
||||
if (!window || !window.screen) return { x: x, y: y }
|
||||
|
||||
if (root.position === "bottom")
|
||||
@@ -212,6 +203,81 @@ Item {
|
||||
return { x: x, y: y }
|
||||
}
|
||||
|
||||
function barDragScreenPoint(scenePoint) {
|
||||
return windowScreenPoint(scenePoint, barDragWindow)
|
||||
}
|
||||
|
||||
// Split the screen along its diagonals (in normalized space, so widescreens
|
||||
// don't bias toward left/right): whichever triangle holds the cursor names
|
||||
// the candidate edge.
|
||||
function nearestScreenEdge(point, screen) {
|
||||
var nx = screen.width > 0 ? Util.clamp(point.x / screen.width, 0, 1) : 0.5
|
||||
var ny = screen.height > 0 ? Util.clamp(point.y / screen.height, 0, 1) : 0.5
|
||||
|
||||
var edge = "top"
|
||||
var best = ny
|
||||
if (1 - ny < best) { edge = "bottom"; best = 1 - ny }
|
||||
if (nx < best) { edge = "left"; best = nx }
|
||||
if (1 - nx < best) { edge = "right"; best = 1 - nx }
|
||||
return edge
|
||||
}
|
||||
|
||||
function beginBarMove(window) {
|
||||
barMoveSettleTimer.stop()
|
||||
barMoveSettling = false
|
||||
barMoveWindow = window
|
||||
barMoveScreen = window ? window.screen : null
|
||||
barMoveCandidate = position
|
||||
barMoveActive = true
|
||||
}
|
||||
|
||||
function updateBarMove(screenPoint) {
|
||||
if (!barMoveActive || !barMoveScreen) return
|
||||
barMoveCandidate = nearestScreenEdge(screenPoint, barMoveScreen)
|
||||
}
|
||||
|
||||
function clearBarMove() {
|
||||
barMoveSettleTimer.stop()
|
||||
barMoveActive = false
|
||||
barMoveSettling = false
|
||||
barMoveCandidate = ""
|
||||
barMoveWindow = null
|
||||
barMoveScreen = null
|
||||
}
|
||||
|
||||
function finishBarMove() {
|
||||
var edge = barMoveCandidate
|
||||
if (!barMoveActive || !edge || edge === position) {
|
||||
clearBarMove()
|
||||
return
|
||||
}
|
||||
|
||||
// Hold the ghost on the target edge while the config round-trips and the
|
||||
// bar re-anchors, so the handoff doesn't flash an empty edge.
|
||||
barMoveActive = false
|
||||
barMoveSettling = true
|
||||
barMoveSettleTimer.restart()
|
||||
setBarPosition(edge)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: barMoveSettleTimer
|
||||
interval: 450
|
||||
onTriggered: root.clearBarMove()
|
||||
}
|
||||
|
||||
function setBarPosition(value) {
|
||||
var next = normalizePosition(value)
|
||||
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
||||
root.shell.mutateShellConfig(function(config) {
|
||||
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
||||
config.bar.position = next
|
||||
})
|
||||
} else {
|
||||
root.position = next
|
||||
}
|
||||
}
|
||||
|
||||
function captureBarDragGhost(slot) {
|
||||
var item = slot && slot.activeItem ? slot.activeItem : null
|
||||
barDragImageUrl = ""
|
||||
@@ -441,16 +507,6 @@ Item {
|
||||
launcher.startDetached()
|
||||
}
|
||||
|
||||
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() {
|
||||
var nextTransparent = !(root.requestedTransparent === true)
|
||||
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
||||
@@ -804,6 +860,19 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Component {
|
||||
BarMoveGhostPanel {
|
||||
required property var modelData
|
||||
|
||||
screen: modelData
|
||||
ghostScreen: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component BarPanel: PanelWindow {
|
||||
id: barWindow
|
||||
|
||||
@@ -994,6 +1063,59 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
component BarMoveGhostPanel: PanelWindow {
|
||||
id: moveGhostWindow
|
||||
|
||||
required property var ghostScreen
|
||||
readonly property bool screenMatches: root.barMoveScreen === ghostScreen ||
|
||||
(root.barMoveScreen && ghostScreen && root.barMoveScreen.name && ghostScreen.name && root.barMoveScreen.name === ghostScreen.name)
|
||||
visible: (root.barMoveActive || root.barMoveSettling) && screenMatches
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "omarchy-bar-move-ghost"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// Visual-only preview of the candidate edge. Keep the input region empty
|
||||
// so the overlay never steals the gesture area's active pointer grab.
|
||||
mask: Region {}
|
||||
|
||||
// One fixed-geometry slab per edge, crossfaded on candidate changes.
|
||||
// Resizing a single slab between edges repaints mid-transition and
|
||||
// flickers; fading between static ones does not.
|
||||
Repeater {
|
||||
model: ["top", "bottom", "left", "right"]
|
||||
|
||||
BorderSurface {
|
||||
id: edgeSlab
|
||||
|
||||
required property string modelData
|
||||
readonly property bool edgeVertical: modelData === "left" || modelData === "right"
|
||||
readonly property int edgeSize: edgeVertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal
|
||||
|
||||
x: modelData === "right" ? parent.width - edgeSize : 0
|
||||
y: modelData === "bottom" ? parent.height - edgeSize : 0
|
||||
width: edgeVertical ? edgeSize : parent.width
|
||||
height: edgeVertical ? parent.height : edgeSize
|
||||
color: root.transparent ? "transparent" : root.background
|
||||
borderSpec: Border.flat(root.barForeground, 1)
|
||||
visible: opacity > 0
|
||||
opacity: root.barMoveCandidate === modelData ? (root.transparent ? 0.45 : 0.7) : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findCenterAnchorEntry() {
|
||||
var entries = root.layoutEntries("center")
|
||||
var idx = root.entryIndex(entries, root.centerAnchor)
|
||||
@@ -1045,7 +1167,7 @@ Item {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||
region: "center"
|
||||
anchors.right: centerConfigControl.visible ? centerConfigControl.left : centerAnchorModule.left
|
||||
anchors.right: centerAnchorModule.left
|
||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||
}
|
||||
|
||||
@@ -1057,16 +1179,6 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BarConfigControl {
|
||||
id: centerConfigControl
|
||||
|
||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||
clockHovered: centerAnchorModule.hovered
|
||||
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
||||
anchors.right: centerAnchorModule.left
|
||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||
}
|
||||
|
||||
ModuleList {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||
@@ -1100,7 +1212,7 @@ Item {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
||||
region: "center"
|
||||
anchors.bottom: centerConfigControl.visible ? centerConfigControl.top : centerAnchorModule.top
|
||||
anchors.bottom: centerAnchorModule.top
|
||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||
}
|
||||
|
||||
@@ -1112,16 +1224,6 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BarConfigControl {
|
||||
id: centerConfigControl
|
||||
|
||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||
clockHovered: centerAnchorModule.hovered
|
||||
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
||||
anchors.bottom: centerAnchorModule.top
|
||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||
}
|
||||
|
||||
ModuleList {
|
||||
visible: centerRoot.hasAnchor
|
||||
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
||||
@@ -1134,91 +1236,80 @@ Item {
|
||||
}
|
||||
|
||||
component CenterGestureArea: MouseArea {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
id: gestureArea
|
||||
|
||||
onDoubleClicked: function(mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.toggleTransparency()
|
||||
property bool dragging: false
|
||||
property bool suppressClick: false
|
||||
property real pressedX: 0
|
||||
property real pressedY: 0
|
||||
readonly property real dragThreshold: Style.space(4)
|
||||
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: dragging ? Qt.ClosedHandCursor : Qt.ArrowCursor
|
||||
pressAndHoldInterval: 200
|
||||
|
||||
function startDrag(x, y) {
|
||||
if (dragging) return
|
||||
dragging = true
|
||||
root.beginBarMove(root.targetWindow(gestureArea))
|
||||
var scenePoint = gestureArea.mapToItem(null, x, y)
|
||||
root.updateBarMove(root.windowScreenPoint(scenePoint, root.barMoveWindow))
|
||||
}
|
||||
|
||||
onPressed: function(mouse) {
|
||||
dragging = false
|
||||
suppressClick = false
|
||||
pressedX = mouse.x
|
||||
pressedY = mouse.y
|
||||
}
|
||||
|
||||
onPressAndHold: function(mouse) {
|
||||
startDrag(mouse.x, mouse.y)
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (!(mouse.buttons & Qt.LeftButton)) return
|
||||
|
||||
if (!dragging) {
|
||||
var distance = Math.abs(mouse.x - pressedX) + Math.abs(mouse.y - pressedY)
|
||||
if (distance < dragThreshold) return
|
||||
startDrag(mouse.x, mouse.y)
|
||||
return
|
||||
}
|
||||
|
||||
var scenePoint = gestureArea.mapToItem(null, mouse.x, mouse.y)
|
||||
root.updateBarMove(root.windowScreenPoint(scenePoint, root.barMoveWindow))
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (!dragging) return
|
||||
dragging = false
|
||||
suppressClick = true
|
||||
root.finishBarMove()
|
||||
mouse.accepted = true
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
dragging = false
|
||||
suppressClick = false
|
||||
root.clearBarMove()
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (suppressClick) {
|
||||
suppressClick = false
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component BarConfigControl: Item {
|
||||
id: configControl
|
||||
|
||||
property bool clockHovered: false
|
||||
property bool centerHovered: false
|
||||
property bool openWhenReady: false
|
||||
|
||||
readonly property var panelItem: configPanelLoader.item
|
||||
readonly property bool panelOpen: panelItem ? panelItem.opened === true : false
|
||||
readonly property bool revealed: visible && (clockHovered || centerHovered || controlHover.hovered || panelOpen)
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
z: 500
|
||||
|
||||
HoverHandler { id: controlHover }
|
||||
|
||||
Component.onCompleted: root.registerConfigControl(configControl)
|
||||
Component.onDestruction: root.unregisterConfigControl(configControl)
|
||||
|
||||
function configurePanel(panel) {
|
||||
if (!panel) return
|
||||
panel.bar = root
|
||||
panel.anchorItem = button
|
||||
}
|
||||
|
||||
function openPanel() {
|
||||
if (!panelItem) {
|
||||
openWhenReady = true
|
||||
onDoubleClicked: function(mouse) {
|
||||
if (suppressClick) {
|
||||
suppressClick = false
|
||||
return
|
||||
}
|
||||
panelItem.open()
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
if (!panelItem) {
|
||||
openPanel()
|
||||
return
|
||||
}
|
||||
panelItem.toggle()
|
||||
}
|
||||
|
||||
WidgetButton {
|
||||
id: button
|
||||
|
||||
anchors.fill: parent
|
||||
bar: root
|
||||
text: ""
|
||||
keepSpace: true
|
||||
concealed: !configControl.revealed
|
||||
dimmed: configControl.revealed && !controlHover.hovered && !configControl.panelOpen
|
||||
interactive: configControl.revealed
|
||||
horizontalMargin: 6.5
|
||||
verticalPadding: 6
|
||||
fixedWidth: vertical ? -1 : Style.bar.statusSlot
|
||||
fixedHeight: vertical ? Style.bar.statusSlot : -1
|
||||
tooltipText: "Bar config"
|
||||
onPressed: function(b) {
|
||||
if (b === Qt.LeftButton) configControl.togglePanel()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: configPanelLoader
|
||||
|
||||
active: true
|
||||
source: Qt.resolvedUrl("BarConfigPanel.qml")
|
||||
onLoaded: {
|
||||
configControl.configurePanel(item)
|
||||
if (configControl.openWhenReady) {
|
||||
configControl.openWhenReady = false
|
||||
item.open()
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.toggleTransparency()
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
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 the inline bar config panel, `omarchy bar ...`, 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 bar gestures, `omarchy bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge.
|
||||
|
||||
Open quick position and transparency controls with `omarchy 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 bar plugin add`, `omarchy bar plugin move`, `omarchy bar plugin remove`, and `omarchy bar plugin set` (widget ids come from `omarchy plugin list`). Double-left-click empty center-bar space to toggle bar transparency.
|
||||
The bar is configured directly on the bar itself: drag empty bar space (or click-and-hold) to move the bar to another screen edge, double-left-click empty center-bar space to toggle transparency, and drag widgets to reorder them. The `omarchy bar position` and `omarchy bar transparent` commands do the same from scripts. For scriptable widget changes, use `omarchy bar plugin add`, `omarchy bar plugin move`, `omarchy bar plugin remove`, and `omarchy bar plugin set` (widget ids come from `omarchy plugin list`).
|
||||
|
||||
Example `shell.json` (bar subtree only shown):
|
||||
|
||||
|
||||
@@ -909,10 +909,6 @@ ShellRoot {
|
||||
return JSON.stringify(shell.bar && shell.bar.debugBarGeometry ? shell.bar.debugBarGeometry() : [])
|
||||
}
|
||||
|
||||
function openBarConfig(): string {
|
||||
return shell.bar && shell.bar.openConfigPanel && shell.bar.openConfigPanel() ? "ok" : "unknown"
|
||||
}
|
||||
|
||||
function summon(id: string, payloadJson: string): string {
|
||||
return shell.summon(id, payloadJson) ? "ok" : "unknown"
|
||||
}
|
||||
|
||||
@@ -116,7 +116,6 @@ jq -e '
|
||||
}
|
||||
pass "shell IPC returns effective shell config"
|
||||
|
||||
[[ $(shell_ipc shell openBarConfig) == "ok" ]] || fail_with_log "shell IPC opens bar config panel"
|
||||
[[ $(shell_ipc shell summon omarchy.launcher '{"query":"term"}') == "ok" ]] || fail_with_log "shell IPC summons launcher overlay"
|
||||
shell_ipc_quiet shell hide omarchy.launcher >/dev/null
|
||||
[[ $(shell_ipc shell summon missing.plugin "{}") == "unknown" ]] || fail_with_log "shell IPC rejects unknown plugin"
|
||||
|
||||
Reference in New Issue
Block a user