Extract panels out into their own top-level concern

This commit is contained in:
David Heinemeier Hansson
2026-05-20 13:51:39 +02:00
parent 3b9700682c
commit ea1b0e7ced
23 changed files with 154 additions and 152 deletions
+3 -3
View File
@@ -59,8 +59,8 @@ PanelWindow {
readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null
readonly property string barPos: bar ? bar.position : "top"
function closePopout() {
if (owner && "closePopout" in owner) owner.closePopout()
function close() {
if (owner && "close" in owner) owner.close()
else root.open = false
}
@@ -255,7 +255,7 @@ PanelWindow {
onExited: hoveringBar = false
onClicked: function(mouse) {
if (inBarRegion(mouse.x, mouse.y) && forwardBarClick(mouse.x, mouse.y, mouse.button)) return
root.closePopout()
root.close()
}
}
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import Quickshell.Io
// Base item for shell panels. Panels are not bar widgets, but the bar may host
// or toggle them and injects the same ambient context while doing so. The base
// owns the shared IPC-backed open/close lifecycle; panel implementations own
// their button behavior, keyboard navigation, and content.
Item {
id: root
property QtObject bar: null
property string moduleName: ""
property var settings: ({})
property string ipcTarget: ""
property bool manageIpc: true
property alias controller: panelController
readonly property bool opened: panelController.open
function open() { panelController.show() }
function close() { panelController.hide() }
function toggle() { opened ? close() : open() }
PanelController {
id: panelController
}
property IpcHandler _ipc: manageIpc ? ipcComponent.createObject(root) : null
property Component ipcComponent: Component {
IpcHandler {
target: root.ipcTarget
function open(): void { root.open() }
function close(): void { root.close() }
function show(): void { root.open() }
function hide(): void { root.close() }
function toggle(): void { root.toggle() }
}
}
}
+4 -37
View File
@@ -1,49 +1,16 @@
import QtQuick
import Quickshell
import Quickshell.Io
// Owns the open/close lifecycle for a bar panel widget. Wraps the
// repetitive popupOpen + closePopout + toggle/show/hide IpcHandler triplet
// so each panel just declares one of these and binds its WidgetButton +
// KeyboardPanel to the exposed `open` property.
// Stores the open state for a shell panel. Panel owns the public lifecycle
// methods and IPC wiring; this object only keeps state separate from the
// panel implementation's own properties.
//
// Usage:
// PanelController { id: ctrl; ipcTarget: "audioPanel" }
//
// WidgetButton { onPressed: ctrl.toggle() }
// KeyboardPanel { open: ctrl.open; owner: ctrl; focusTarget: keyCatcher }
//
// The bar popout coordinator uses `owner` as a registry key, so each
// PanelController instance doubles as that key. KeyboardPanel calls
// `owner.closePopout()` when another panel grabs the slot.
//
// Set `manageIpc: false` when the panel needs to declare its own IpcHandler
// for additional methods on the same target (monitorPanel adds brightness +
// state). Quickshell only honors one IpcHandler per target, so the panel's
// handler must then also delegate toggle/show/hide to this controller.
// PanelController { id: panelController }
QtObject {
id: root
// IPC target name. The bar pairs this with the bar widget's filename so a
// Hyprland keybind (`omarchy-shell <target> toggle`) summons the panel.
property string ipcTarget: ""
property bool manageIpc: true
property bool open: false
function toggle() { open = !open }
function show() { if (!open) open = true }
function hide() { open = false }
function closePopout() { open = false }
property IpcHandler _ipc: manageIpc ? ipcComponent.createObject(root) : null
property Component ipcComponent: Component {
IpcHandler {
target: root.ipcTarget
function toggle(): void { root.toggle() }
function show(): void { root.show() }
function hide(): void { root.hide() }
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ import QtQuick
// anchors.fill: parent
// onMoveRequested: function(dx, dy) { root.moveCursor(dx, dy) }
// onActivateRequested: root.activateCursor()
// onCloseRequested: root.closePopout()
// onCloseRequested: root.close()
// onDeleteRequested: root.deleteSelected()
// onTextKey: function(t) { if (t === "r") root.refresh() }
//
+3 -3
View File
@@ -55,8 +55,8 @@ PopupWindow {
return Math.round(Math.min(desired, maxHeight))
}
function closePopout() {
if (owner && "closePopout" in owner) owner.closePopout()
function close() {
if (owner && "close" in owner) owner.close()
else root.open = false
}
@@ -80,7 +80,7 @@ PopupWindow {
HyprlandFocusGrab {
active: root.open && root.triggerMode === "click"
windows: root.anchorWindow ? [root, root.anchorWindow] : [root]
onCleared: root.closePopout()
onCleared: root.close()
}
anchor {
+1
View File
@@ -8,6 +8,7 @@ CursorSurface 1.0 CursorSurface.qml
Dropdown 1.0 Dropdown.qml
KeyboardPanel 1.0 KeyboardPanel.qml
NumberField 1.0 NumberField.qml
Panel 1.0 Panel.qml
PanelActionButton 1.0 PanelActionButton.qml
PanelController 1.0 PanelController.qml
PanelKeyCatcher 1.0 PanelKeyCatcher.qml