mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
Replace bar settings with inline config panel
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Launch the Omarchy bar settings panel
|
||||
# omarchy:summary=Open the Omarchy bar config panel
|
||||
|
||||
exec omarchy-shell shell summon omarchy.settings "{}"
|
||||
exec omarchy-shell shell openBarConfig
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
# omarchy:summary=Manage Omarchy shell plugins and bar widgets
|
||||
# omarchy:group=plugin
|
||||
# omarchy:args=<list|rescan|enable|disable|clone|edit|bar> [...]
|
||||
# omarchy:examples=omarchy plugin list | omarchy plugin clone omarchy.clock local.clock --with ai | omarchy plugin edit local.clock --with editor | omarchy plugin enable acme.weather --section right | omarchy plugin bar edit
|
||||
# omarchy:examples=omarchy plugin list | omarchy plugin clone omarchy.clock local.clock --with ai | omarchy plugin edit local.clock --with editor | omarchy plugin enable acme.weather --section right | omarchy plugin bar settings
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -30,7 +30,7 @@ Clone options:
|
||||
--add [placement] Add cloned bar widget to the bar
|
||||
|
||||
Bar widget commands:
|
||||
bar edit Open the visual bar settings panel
|
||||
bar settings Open the inline bar config panel
|
||||
bar list [--json] List current bar layout
|
||||
bar add <id> [placement] [--duplicate]
|
||||
bar move <id> [placement] [--from-section SECTION] [--from-index N]
|
||||
@@ -1136,7 +1136,7 @@ bar_command() {
|
||||
|
||||
case "$command" in
|
||||
edit | settings)
|
||||
(( $# == 0 )) || fail "bar edit does not take arguments"
|
||||
(( $# == 0 )) || fail "bar settings does not take arguments"
|
||||
exec omarchy-launch-bar-settings
|
||||
;;
|
||||
list)
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ Options:
|
||||
|
||||
Examples:
|
||||
omarchy-shell shell ping
|
||||
omarchy-shell shell summon omarchy.settings "{}"
|
||||
omarchy-shell shell openBarConfig
|
||||
omarchy-shell -q Indicators refresh
|
||||
omarchy-shell shell listPlugins
|
||||
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
-- Window and layer rules for the Omarchy Quickshell surfaces. The
|
||||
-- shell-wide bar / menu / popouts are layer-shell; the bar settings panel
|
||||
-- is a regular Hyprland window via Quickshell's FloatingWindow.
|
||||
-- shell-wide bar / menu / popouts are layer-shell.
|
||||
|
||||
-- Keep the bar instant: no layer-shell fade/slide animation.
|
||||
hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation = "none" })
|
||||
@@ -10,22 +9,6 @@ hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation
|
||||
-- QML opacity transition for normal open/close, and skip it for panel handoff.
|
||||
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-launcher|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel)$" }, no_anim = true, animation = "none" })
|
||||
|
||||
-- Bar settings floats centered with a sensible default size instead of
|
||||
-- tiling — it's a transient dialog, not a workspace surface.
|
||||
o.window({
|
||||
class = "^org.quickshell$",
|
||||
title = "^Omarchy Bar Settings$",
|
||||
}, {
|
||||
float = true,
|
||||
center = true,
|
||||
size = { 760, 620 },
|
||||
})
|
||||
|
||||
-- Dev gallery is the main shell workbench; open it maximized like
|
||||
-- SUPER+ALT+F so component previews have the whole workspace.
|
||||
o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true })
|
||||
|
||||
-- Per-widget settings dialog opens as a smaller FloatingWindow off the
|
||||
-- bar settings panel; keep it floating with its own default size.
|
||||
o.window({ class = "^org.quickshell$", title = "^Widget settings " }, { float = true })
|
||||
o.window({ class = "^org.quickshell$", title = "^Widget settings " }, { size = { 380, 320 } })
|
||||
|
||||
@@ -26,7 +26,7 @@ first call.
|
||||
| Kind | What it is |
|
||||
|--------------|---------------------------------------------|
|
||||
| `bar-widget` | Component the bar drops into a section |
|
||||
| `panel` | Floating window (e.g. bar settings) |
|
||||
| `panel` | Floating window (e.g. OSD) |
|
||||
| `overlay` | Fullscreen overlay (e.g. background picker) |
|
||||
| `menu` | Summoned menu surface |
|
||||
| `service` | Headless singleton, no UI |
|
||||
@@ -43,8 +43,7 @@ Full schema: [`shell/services/PluginRegistry.qml`](../shell/services/PluginRegis
|
||||
plus the QML referenced from `entryPoints`.
|
||||
2. `omarchy plugin rescan`
|
||||
3. `omarchy plugin enable <id>`
|
||||
4. Bar widgets also need adding to a section with `omarchy plugin bar add <id>`
|
||||
or the visual editor (`omarchy plugin bar edit`).
|
||||
4. Bar widgets also need adding to a section with `omarchy plugin bar add <id>`.
|
||||
|
||||
The lower-level IPC methods remain available through `omarchy-shell shell ...`
|
||||
for callers that need to talk directly to the running shell.
|
||||
|
||||
@@ -83,7 +83,7 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Layout normalization shared by the bar host and the bar settings panel
|
||||
// Layout normalization shared by bar config consumers
|
||||
// so the two never drift. Entries are deep-cloned to decouple from the
|
||||
// input config; consumers can mutate without leaking back to shell.json.
|
||||
function normalizeLayoutEntry(entry) {
|
||||
|
||||
+6
-11
@@ -2,9 +2,8 @@
|
||||
|
||||
`omarchy-shell` is a single long-running [Quickshell](https://quickshell.org/)
|
||||
instance that hosts the Omarchy desktop. Hyprland autostart launches one shell
|
||||
per graphical session; everything else — the bar, the bar settings UI, the
|
||||
background switcher, future panels and overlays — runs **inside** the shell as
|
||||
a plugin.
|
||||
per graphical session; everything else — the bar, background switcher, panels,
|
||||
and overlays — runs **inside** the shell as a plugin.
|
||||
|
||||
Hosting everything inside one shell means:
|
||||
|
||||
@@ -24,7 +23,6 @@ shell/
|
||||
BarWidgetRegistry.qml unified registry for bar widgets (1p + 3p)
|
||||
plugins/
|
||||
bar/ first-party plugins (see plugins/README.md)
|
||||
settings/ settings panel + plugin-declared widget forms
|
||||
launcher/
|
||||
image-picker/
|
||||
menu/
|
||||
@@ -77,7 +75,7 @@ Supported `kinds`:
|
||||
| Kind | What it is |
|
||||
|--------------|--------------------------------------------------------------|
|
||||
| `bar-widget` | A component that the bar can drop into a section |
|
||||
| `panel` | A persistent or summoned floating window (e.g. bar settings) |
|
||||
| `panel` | A persistent or summoned floating window (e.g. OSD) |
|
||||
| `overlay` | A fullscreen overlay (e.g. background switcher) |
|
||||
| `menu` | A summoned menu surface |
|
||||
| `service` | A headless singleton, no UI |
|
||||
@@ -97,8 +95,7 @@ The full schema lives in `services/PluginRegistry.qml`.
|
||||
referenced from its `entryPoints`.
|
||||
2. `omarchy plugin rescan`.
|
||||
3. Enable the plugin with `omarchy plugin enable <id>`.
|
||||
4. If it's a `bar-widget`, place it with `omarchy plugin bar add <id>` or
|
||||
open the visual editor with `omarchy plugin bar edit`.
|
||||
4. If it's a `bar-widget`, place it with `omarchy plugin bar add <id>`.
|
||||
|
||||
The lower-level IPC equivalents are still available via `omarchy-shell shell rescanPlugins`,
|
||||
`omarchy-shell shell setPluginEnabled <id> true`, and `omarchy-shell shell listPlugins`.
|
||||
@@ -153,7 +150,7 @@ calls to the running shell. It does not start the shell.
|
||||
|
||||
```
|
||||
omarchy-shell shell ping
|
||||
omarchy-shell shell summon omarchy.settings "{}"
|
||||
omarchy-shell shell openBarConfig
|
||||
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
|
||||
omarchy-shell shell listPlugins
|
||||
omarchy-shell shell rescanPlugins
|
||||
@@ -177,9 +174,7 @@ customization from the shipped defaults lives in it.
|
||||
The `config/omarchy/shell.json` default config describes the
|
||||
fresh-install state. When the user has no `shell.json`, the shell uses
|
||||
the defaults verbatim. Once the user customizes anything, `shell.json`
|
||||
becomes the authoritative file — we do **not** deep-merge defaults back
|
||||
in. Pressing **Reset bar to defaults** in `omarchy launch bar settings`
|
||||
rewrites the `bar` subtree from the current default shell config.
|
||||
becomes the authoritative file — we do **not** deep-merge defaults back in.
|
||||
|
||||
### shell.json shape
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ Item {
|
||||
QQC.Popup {
|
||||
id: popup
|
||||
// Reparent to the window's content item so the popup is free of any
|
||||
// clipping ancestor (e.g. the bar settings dialog Flickable). Position
|
||||
// clipping ancestor. Position
|
||||
// and available height are recomputed on open and any time the
|
||||
// trigger's geometry changes, since a binding on mapToItem alone
|
||||
// won't reliably re-evaluate when ancestors scroll or resize.
|
||||
|
||||
@@ -15,7 +15,7 @@ import qs.Commons
|
||||
//
|
||||
// Set `focusable: true` to make the button keyboard-tabbable with the
|
||||
// shared hover-cursor/focus tokens. Use this
|
||||
// in form contexts (the bar settings widget cards) where Tab walks a list
|
||||
// in form contexts where Tab walks a list
|
||||
// of controls; leave it false for the right-edge actions on panel rows
|
||||
// where the row's CursorSurface owns the keyboard cursor.
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@ import qs.Commons
|
||||
// Searchable single-select dropdown. Same trigger shape as Dropdown, but
|
||||
// the popup leads with an embedded TextField that filters the option
|
||||
// list in real time. Use for pickers with enough options that scanning
|
||||
// is friction (e.g. bar settings "+ Add widget").
|
||||
// is friction.
|
||||
//
|
||||
// Filtering is case-insensitive substring against each option's label.
|
||||
// Options can be string[] or [{ value, label, description? }] — the same
|
||||
|
||||
@@ -12,7 +12,6 @@ User-installed plugins live alongside these conceptually but on disk under
|
||||
| Plugin | id | kinds | entry point |
|
||||
|---------------|---------------------------|-------------------------|---------------------------------------|
|
||||
| Bar | `omarchy.bar` | `bar` | `bar/Bar.qml` |
|
||||
| Bar settings | `omarchy.settings` | `panel` | `settings/SettingsPanel.qml` |
|
||||
| Launcher | `omarchy.launcher` | `overlay` | `launcher/Launcher.qml` |
|
||||
| Image picker | `omarchy.image-picker` | `overlay` | `image-picker/ImagePicker.qml` |
|
||||
| Emojis | `omarchy.emojis` | `overlay` | `emojis/Emojis.qml` |
|
||||
@@ -46,17 +45,6 @@ providing [`config/omarchy/shell.json`](../../config/omarchy/shell.json) when
|
||||
the user has no file). See [`bar/README.md`](bar/README.md) for the widget catalogue
|
||||
and customization schema.
|
||||
|
||||
## Bar settings
|
||||
|
||||
Visual editor for the bar layout. Summoned by
|
||||
`omarchy-shell shell summon omarchy.settings "{}"` (which is what
|
||||
`omarchy launch bar settings` ultimately calls). Provides:
|
||||
|
||||
- bar position and center-anchor controls
|
||||
- per-section add/move/remove/edit of bar widget entries
|
||||
- dynamic per-widget settings forms that write inline back to the
|
||||
corresponding shell.json entry
|
||||
|
||||
## Launcher
|
||||
|
||||
Quickshell-powered launcher. It uses Quickshell's native
|
||||
|
||||
+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):
|
||||
|
||||
|
||||
@@ -1550,7 +1550,7 @@ Item {
|
||||
font.bold: true
|
||||
}
|
||||
Text {
|
||||
text: "Dropdown with an embedded filter input. Type to narrow the list, Down to jump from the search to the first match, Enter to select. Use this for the bar settings \"+ Add widget\" picker and any other long-list selector."
|
||||
text: "Dropdown with an embedded filter input. Type to narrow the list, Down to jump from the search to the first match, Enter to select. Use this for long option lists where plain scrolling is friction."
|
||||
color: Qt.darker(root.foreground, 1.5)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
|
||||
@@ -29,7 +29,7 @@ Item {
|
||||
// ~/.cache where regeneratable artifacts belong.
|
||||
readonly property string cacheDir: home + "/.cache/omarchy/"
|
||||
readonly property string imageCacheDir: cacheDir + "notification-images/"
|
||||
// Corner radius is shared with the menu and bar settings panel.
|
||||
// Corner radius is shared with the menu and shell panels.
|
||||
// It mirrors Hyprland's current decoration:rounding value.
|
||||
readonly property int cornerRadius: Style.cornerRadius
|
||||
// Surfaces anchor relative to the omarchy bar so popups and history land
|
||||
|
||||
@@ -35,4 +35,4 @@ Renders the Tailscale mark natively as a theme-colored 3×3 dot grid, matching t
|
||||
|
||||
## Add to the bar
|
||||
|
||||
This widget ships as first-party plugin `omarchy.tailscale`. Add it through Omarchy's bar settings UI, or add an entry such as `{ "id": "omarchy.tailscale" }` to one of the `bar.layout` sections in `~/.config/omarchy/shell.json` and restart the shell.
|
||||
This widget ships as first-party plugin `omarchy.tailscale`. Add it with `omarchy plugin bar add omarchy.tailscale`, or add an entry such as `{ "id": "omarchy.tailscale" }` to one of the `bar.layout` sections in `~/.config/omarchy/shell.json` and restart the shell.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,281 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC
|
||||
import qs.Commons
|
||||
import qs.Ui as Ui
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
signal fieldChanged(string key, var value)
|
||||
|
||||
property var schema: []
|
||||
property var entry: ({})
|
||||
property color foreground: Color.popups.text
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
// Resolved on disk path of the plugin that owns this form, used to
|
||||
// resolve relative argv entries in a multiselect's `optionsCommand`.
|
||||
property string pluginSourceDir: ""
|
||||
|
||||
spacing: Style.spacing.xl
|
||||
width: parent ? parent.width : 0
|
||||
|
||||
function currentValue(field) {
|
||||
if (entry && entry[field.key] !== undefined) return entry[field.key]
|
||||
if (field.defaultValue !== undefined) return field.defaultValue
|
||||
|
||||
switch (field.type) {
|
||||
case "boolean": return false
|
||||
case "integer":
|
||||
case "number": return field.min !== undefined ? field.min : 0
|
||||
case "enum": return field.options && field.options.length > 0 ? field.options[0] : ""
|
||||
case "multiselect": return []
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve a multiselect's optionsCommand argv against the plugin's source
|
||||
// directory. The first entry is treated as a path relative to the plugin
|
||||
// dir unless it is absolute or begins with ".". Subsequent entries pass
|
||||
// through unchanged.
|
||||
// Resolve a multiselect's optionsCommand argv against the plugin's source
|
||||
// directory. The first argv entry must be a relative path inside the
|
||||
// plugin dir (no leading `/`, no `..` segments, no empty segments);
|
||||
// anything else is rejected and the field falls back to whatever static
|
||||
// `options` it has. Subsequent argv entries pass through unchanged. We
|
||||
// avoid `Array.isArray` because schema-supplied arrays sometimes arrive
|
||||
// as QML JSValue lists that don't satisfy it; iterating by `.length`
|
||||
// works for both real arrays and JSValue lists.
|
||||
function resolveOptionsCommand(field) {
|
||||
var oc = field ? field.optionsCommand : undefined
|
||||
if (!oc || typeof oc.length !== "number" || oc.length === 0) return []
|
||||
var argv = []
|
||||
for (var i = 0; i < oc.length; i++) argv.push(String(oc[i]))
|
||||
if (!pluginSourceDir) return []
|
||||
var head = argv[0]
|
||||
if (head.length === 0 || head.charAt(0) === "/") {
|
||||
console.warn("DynamicSettingsForm: optionsCommand must be a path relative to the plugin dir, got: " + head)
|
||||
return []
|
||||
}
|
||||
var rel = head.replace(/^\.\//, "")
|
||||
var segments = rel.split("/")
|
||||
for (var s = 0; s < segments.length; s++) {
|
||||
if (segments[s] === ".." || segments[s] === "") {
|
||||
console.warn("DynamicSettingsForm: optionsCommand may not contain '..' or empty segments: " + head)
|
||||
return []
|
||||
}
|
||||
}
|
||||
var dir = pluginSourceDir.replace(/\/$/, "")
|
||||
argv[0] = dir + "/" + rel
|
||||
return argv
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.schema
|
||||
|
||||
Column {
|
||||
required property var modelData
|
||||
|
||||
width: root.width
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
visible: text !== "" && String(modelData && modelData.type ? modelData.type : "") !== "boolean"
|
||||
text: modelData && modelData.label ? modelData.label : (modelData && modelData.key ? modelData.key : "")
|
||||
color: Qt.darker(root.foreground, 1.3)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !!(modelData && modelData.description) && String(modelData && modelData.type ? modelData.type : "") !== "boolean"
|
||||
text: modelData ? (modelData.description || "") : ""
|
||||
color: Qt.darker(root.foreground, 1.6)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
height: item ? item.implicitHeight : 0
|
||||
sourceComponent: {
|
||||
if (!modelData || !modelData.type) return stringField
|
||||
switch (String(modelData.type)) {
|
||||
case "boolean": return booleanField
|
||||
case "enum": return enumField
|
||||
case "integer": return integerField
|
||||
case "number": return numberField
|
||||
case "multiselect": return multiselectField
|
||||
default: return stringField
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if (!item) return
|
||||
item.fieldKey = modelData.key
|
||||
item.field = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.schema || root.schema.length === 0
|
||||
text: "No settings."
|
||||
color: Qt.darker(root.foreground, 1.5)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stringField
|
||||
|
||||
Ui.TextField {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
|
||||
width: parent.width
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
text: root.currentValue(field) === undefined ? "" : String(root.currentValue(field))
|
||||
|
||||
onEditingFinished: if (fieldKey) root.fieldChanged(fieldKey, text)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: booleanField
|
||||
|
||||
Ui.Toggle {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
|
||||
width: parent.width
|
||||
label: field && field.label ? String(field.label) : "Enabled"
|
||||
description: field && field.description ? String(field.description) : ""
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
checked: !!root.currentValue(field)
|
||||
|
||||
onClicked: if (fieldKey) root.fieldChanged(fieldKey, !checked)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: enumField
|
||||
|
||||
Ui.Dropdown {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
|
||||
width: parent.width
|
||||
showLabel: false
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
options: field && field.options ? field.options : []
|
||||
value: String(root.currentValue(field))
|
||||
|
||||
onChanged: function(value) {
|
||||
if (fieldKey) root.fieldChanged(fieldKey, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: integerField
|
||||
|
||||
Ui.NumberField {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
from: field && field.min !== undefined ? field.min : 0
|
||||
to: field && field.max !== undefined ? field.max : 9999
|
||||
stepSize: field && field.step !== undefined ? field.step : 1
|
||||
value: {
|
||||
var v = root.currentValue(field)
|
||||
var n = typeof v === "number" ? v : parseInt(String(v || 0), 10)
|
||||
return isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
onModified: function(value) { if (fieldKey) root.fieldChanged(fieldKey, value) }
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: multiselectField
|
||||
|
||||
Ui.MultiSelect {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
|
||||
width: parent.width
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
showLabel: false
|
||||
// MultiSelect's arrayFrom() tolerates JSValue-style schema arrays, so
|
||||
// we can hand it `field.options` directly without an Array.isArray
|
||||
// guard that would silently drop JSValue lists.
|
||||
options: field ? (field.options || []) : []
|
||||
optionsCommand: root.resolveOptionsCommand(field)
|
||||
optionsCommandCwd: root.pluginSourceDir
|
||||
placeholderText: field && field.placeholderText ? String(field.placeholderText) : "Search..."
|
||||
emptyText: field && field.emptyText ? String(field.emptyText) : "No options"
|
||||
noSelectionText: field && field.noSelectionText ? String(field.noSelectionText) : "None selected"
|
||||
values: {
|
||||
var v = root.currentValue(field)
|
||||
return v ? v : []
|
||||
}
|
||||
|
||||
onChanged: function(arr) { if (fieldKey) root.fieldChanged(fieldKey, arr) }
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: numberField
|
||||
|
||||
Row {
|
||||
property string fieldKey: ""
|
||||
property var field: ({})
|
||||
property real currentNumber: {
|
||||
var v = root.currentValue(field)
|
||||
var n = typeof v === "number" ? v : parseFloat(String(v || 0))
|
||||
return isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
spacing: Style.spacing.rowGap
|
||||
|
||||
QQC.Slider {
|
||||
id: slider
|
||||
|
||||
width: parent.width - readout.width - Style.spacing.rowGap
|
||||
from: field && field.min !== undefined ? field.min : 0
|
||||
to: field && field.max !== undefined ? field.max : 1
|
||||
stepSize: field && field.step !== undefined ? field.step : 0.01
|
||||
value: parent.currentNumber
|
||||
|
||||
onMoved: if (parent.fieldKey) root.fieldChanged(parent.fieldKey, value)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: readout
|
||||
|
||||
text: slider.value.toFixed(2)
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
// Themed ComboBox + popup. Anchors below the trigger, paints with the host
|
||||
// shell's foreground/background palette, and inherits the shell-wide corner
|
||||
// radius so nothing renders rounded when the user has set sharp corners.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
property var options: []
|
||||
property color foreground: Color.popups.text
|
||||
property color background: Color.popups.background
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property int cornerRadius: 0
|
||||
property int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
property bool showLabel: true
|
||||
|
||||
signal changed(string value)
|
||||
|
||||
implicitWidth: Style.spacing.dropdownWidth
|
||||
implicitHeight: showLabel ? rowHeight + Style.spacing.huge : rowHeight
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
visible: root.showLabel && root.label !== ""
|
||||
text: root.label
|
||||
color: Qt.darker(root.foreground, 1.4)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: combo
|
||||
width: parent.width
|
||||
height: root.rowHeight
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
model: root.options
|
||||
currentIndex: {
|
||||
for (var i = 0; i < model.length; i++) if (model[i] === root.value) return i
|
||||
return -1
|
||||
}
|
||||
onActivated: function(index) {
|
||||
if (index >= 0 && index < model.length) root.changed(model[index])
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: combo.activeFocus
|
||||
? Style.focusFillFor(root.foreground, root.accent)
|
||||
: (combo.hovered
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent))
|
||||
border.color: combo.activeFocus
|
||||
? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: (combo.hovered
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent))
|
||||
border.width: combo.activeFocus
|
||||
? Style.focusBorderWidth
|
||||
: (combo.hovered ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
radius: root.cornerRadius
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: Style.spacing.controlGap
|
||||
rightPadding: Style.space(24)
|
||||
text: combo.displayText
|
||||
color: root.foreground
|
||||
font: combo.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: combo.width - width - Style.spacing.controlGap
|
||||
y: combo.topPadding + (combo.availableHeight - height) / 2
|
||||
text: "▾"
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
// Anchor the popup directly under the field, full width, sharp/round
|
||||
// matching the shell radius. Override the native white-with-blue look.
|
||||
y: combo.height
|
||||
width: combo.width
|
||||
implicitHeight: Math.min(contentItem.implicitHeight, root.popupRowHeight * 8)
|
||||
padding: Style.spacing.hairline
|
||||
|
||||
background: Rectangle {
|
||||
color: root.background
|
||||
border.color: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: Style.normalBorderWidth
|
||||
radius: root.cornerRadius
|
||||
}
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: combo.delegateModel
|
||||
currentIndex: combo.highlightedIndex
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: combo.width
|
||||
height: root.popupRowHeight
|
||||
padding: 0
|
||||
|
||||
contentItem: Text {
|
||||
text: String(modelData)
|
||||
color: index === combo.highlightedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
leftPadding: Style.spacing.controlPaddingX
|
||||
rightPadding: Style.spacing.controlPaddingX
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: index === combo.highlightedIndex
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: "transparent"
|
||||
radius: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "omarchy.settings",
|
||||
"name": "Omarchy Bar Settings",
|
||||
"version": "1.0.0",
|
||||
"author": "Omarchy",
|
||||
"description": "Customize the Omarchy bar position and widgets",
|
||||
"kinds": [
|
||||
"panel"
|
||||
],
|
||||
"entryPoints": {
|
||||
"panel": "SettingsPanel.qml"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import QtQuick
|
||||
// Instance, not a singleton — instantiated once by shell.qml and injected into
|
||||
// plugins that need to read or extend the widget catalogue. Relative-path
|
||||
// singleton imports were creating per-importer instances which prevented the
|
||||
// bar settings panel from seeing what the bar registered.
|
||||
// shell host from seeing what the bar registered.
|
||||
QtObject {
|
||||
id: registry
|
||||
|
||||
|
||||
@@ -651,6 +651,10 @@ 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"
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ done
|
||||
|
||||
jq -e '
|
||||
map(.id) as $ids |
|
||||
all(["omarchy.menu", "omarchy.settings", "omarchy.notifications", "omarchy.clock"][]; $ids | index(.)) and
|
||||
all(["omarchy.menu", "omarchy.notifications", "omarchy.clock", "omarchy.osd"][]; $ids | index(.)) and
|
||||
all(.[]; (.kinds | type == "array") and (.enabled | type == "boolean") and (.firstParty | type == "boolean"))
|
||||
' <<<"$plugins" >/dev/null || {
|
||||
printf 'Plugins:\n%s\n' "$plugins" | jq . >&2
|
||||
@@ -115,8 +115,7 @@ jq -e '
|
||||
}
|
||||
pass "shell IPC returns effective shell config"
|
||||
|
||||
[[ $(shell_ipc shell summon omarchy.settings "{}") == "ok" ]] || fail_with_log "shell IPC summons settings panel"
|
||||
shell_ipc_quiet shell hide omarchy.settings >/dev/null
|
||||
[[ $(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