Promote bar/common to qs.Ui kit module

This commit is contained in:
Ryan Hughes
2026-05-17 22:33:08 -04:00
parent 408dada45d
commit 6b915926b2
25 changed files with 110 additions and 98 deletions
@@ -0,0 +1,12 @@
module qs.Ui
CursorSurface 1.0 CursorSurface.qml
KeyboardPanel 1.0 KeyboardPanel.qml
PanelActionButton 1.0 PanelActionButton.qml
PanelSectionHeader 1.0 PanelSectionHeader.qml
PanelSeparator 1.0 PanelSeparator.qml
PanelSlider 1.0 PanelSlider.qml
PanelToolTip 1.0 PanelToolTip.qml
PillButton 1.0 PillButton.qml
PopupCard 1.0 PopupCard.qml
WidgetButton 1.0 WidgetButton.qml
@@ -10,7 +10,7 @@ import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Commons
import "common" as BarCommon
import qs.Ui
Item {
id: root
@@ -1716,7 +1716,7 @@ Item {
}
}
BarCommon.PopupCard {
PopupCard {
id: managePopup
anchorItem: trayRoot
owner: trayRoot
@@ -1801,7 +1801,7 @@ Item {
elide: Text.ElideRight
}
BarCommon.PillButton {
PillButton {
id: rowPinBtn
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
@@ -1815,7 +1815,7 @@ Item {
onClicked: trayRoot.togglePin(rowRoot.itemId)
}
BarCommon.PillButton {
PillButton {
id: rowHideBtn
anchors.verticalCenter: parent.verticalCenter
anchors.right: rowPinBtn.left
@@ -3,7 +3,7 @@ import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -90,7 +90,7 @@ Item {
// selectedIndex semantics within a section:
// -1 → on the slider row (h/l adjusts volume, m/Enter mute)
// 0..N-1 → on the Nth device/stream row
// Visuals derive from hasCursor/current via Common.CursorSurface, never
// Visuals derive from hasCursor/current via CursorSurface, never
// from containsMouse — that's what keeps the highlight unique across
// keyboard + mouse like wifi does.
property string focusSection: "output"
@@ -348,7 +348,7 @@ Item {
function hide(): void { root.closePopout() }
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -366,7 +366,7 @@ Item {
}
}
Common.KeyboardPanel {
KeyboardPanel {
id: panel
anchorItem: button
owner: root
@@ -440,7 +440,7 @@ Item {
width: parent.width
spacing: 8
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Output"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
@@ -462,7 +462,7 @@ Item {
// Output slider row — itself a cursor target (selectedIndex === -1
// when focusSection === "output"). h/l adjust the value via
// root.adjustVolume; m / Enter toggle mute.
Common.CursorSurface {
CursorSurface {
id: outputSliderRow
width: parent.width
height: outputSliderInner.implicitHeight + 8
@@ -496,7 +496,7 @@ Item {
}
}
Common.Slider {
PanelSlider {
id: outputSlider
bar: root.bar
width: parent.width - outputIconText.width - outputPercent.width - 16
@@ -559,7 +559,7 @@ Item {
width: parent.width
spacing: 8
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Input"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
@@ -578,7 +578,7 @@ Item {
}
}
Common.CursorSurface {
CursorSurface {
id: inputSliderRow
visible: !!root.source
width: parent.width
@@ -613,7 +613,7 @@ Item {
}
}
Common.Slider {
PanelSlider {
id: inputSlider
bar: root.bar
width: parent.width - inputIconText.width - inputPercent.width - 16
@@ -672,7 +672,7 @@ Item {
spacing: 6
visible: root.audioStreams.length > 0
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Playing"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
@@ -701,7 +701,7 @@ Item {
// Output device row — cursor target inside the "output" section. Mouse
// hover updates the panel cursor at the root; visuals come entirely
// from hasCursor/current via CursorSurface, never from containsMouse.
component SinkRow: Common.CursorSurface {
component SinkRow: CursorSurface {
id: sinkRow
required property var node
required property int rowIndex
@@ -767,7 +767,7 @@ Item {
}
// Input device row — sibling of SinkRow for the "input" section.
component SourceRow: Common.CursorSurface {
component SourceRow: CursorSurface {
id: sourceRow
required property var node
required property int rowIndex
@@ -836,7 +836,7 @@ Item {
// The stream has its own slider inline, so h/l from the keyboard
// adjusts THIS stream's volume (not the global output) when the cursor
// sits on this row. Enter/Space mutes the stream.
component StreamRow: Common.CursorSurface {
component StreamRow: CursorSurface {
id: streamRow
required property var node
required property int rowIndex
@@ -906,7 +906,7 @@ Item {
}
}
Common.Slider {
PanelSlider {
bar: root.bar
width: parent.width
minimum: 0
@@ -3,7 +3,7 @@ import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Bluetooth
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -71,7 +71,7 @@ Item {
// them, Enter activates.
// "known" — paired/known device rows; Enter toggles connect.
// "discovered" — unpaired devices visible while scanning; Enter pairs.
// Visuals always come from Common.CursorSurface (hasCursor / current),
// Visuals always come from CursorSurface (hasCursor / current),
// never from containsMouse. Mouse hover updates root cursor state too,
// guaranteeing one highlight on screen.
property string focusSection: "header"
@@ -310,7 +310,7 @@ Item {
function hide(): void { root.closePopout() }
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -322,7 +322,7 @@ Item {
}
}
Common.KeyboardPanel {
KeyboardPanel {
id: panel
anchorItem: button
owner: root
@@ -445,7 +445,7 @@ Item {
}
// Discovered (unpaired) devices, only shown while scanning.
Common.PanelSectionHeader {
PanelSectionHeader {
visible: root.adapter && root.adapter.discovering && root.discoveredDevices.length > 0
text: "Discovered"
foreground: root.bar.foreground
@@ -483,11 +483,11 @@ Item {
}
}
// Header pill — wraps Common.PillButton with cursor-state binding so it
// Header pill — wraps PillButton with cursor-state binding so it
// participates in the same single-cursor model as device rows. We don't
// use Common.CursorSurface here because the pill already provides its
// use CursorSurface here because the pill already provides its
// own active/hover visuals and we want them to layer correctly.
component HeaderPill: Common.PillButton {
component HeaderPill: PillButton {
id: pill
required property int pillIndex
property bool pillEnabled: true
@@ -528,7 +528,7 @@ Item {
// connect that drops back to Disconnected within 10s surfaces as "Failed".
// Now a cursor target: hasCursor binds to root state, mouse hover updates
// root state. The X button on the right is a PanelActionButton.
component DeviceRow: Common.CursorSurface {
component DeviceRow: CursorSurface {
id: row
required property var dev
required property int rowIndex
@@ -660,7 +660,7 @@ Item {
// Explicit close button on any known device. Action depends on state:
// connected -> disconnect, otherwise -> forget the pairing entirely.
Common.PanelActionButton {
PanelActionButton {
id: disconnectBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -1,7 +1,7 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -57,7 +57,7 @@ Item {
onDateChanged: root.now = clockTimer.date
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -76,7 +76,7 @@ Item {
}
}
Common.PopupCard {
PopupCard {
id: popup
anchorItem: button
bar: root.bar
@@ -94,7 +94,7 @@ Item {
width: parent.width
implicitHeight: 28
Common.PillButton {
PillButton {
id: prevButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
@@ -114,7 +114,7 @@ Item {
font.bold: true
}
Common.PillButton {
PillButton {
id: nextButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -53,7 +53,7 @@ Item {
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -2,7 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -71,7 +71,7 @@ Item {
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -113,7 +113,7 @@ Item {
onExited: if (root.bar) root.bar.hideTooltip(root)
}
Common.PopupCard {
PopupCard {
id: popup
anchorItem: root
bar: root.bar
@@ -198,7 +198,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
Common.PillButton {
PillButton {
iconText: "󰒮"
foreground: root.bar.foreground
horizontalPadding: 10
@@ -208,7 +208,7 @@ Item {
onClicked: if (root.activePlayer) root.activePlayer.previous()
}
Common.PillButton {
PillButton {
iconText: root.activePlayer && root.activePlayer.isPlaying ? "󰏤" : "󰐊"
foreground: root.bar.foreground
horizontalPadding: 14
@@ -219,7 +219,7 @@ Item {
onClicked: if (root.activePlayer) root.activePlayer.togglePlaying()
}
Common.PillButton {
PillButton {
iconText: "󰒭"
foreground: root.bar.foreground
horizontalPadding: 10
@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -36,7 +36,7 @@ Item {
PwObjectTracker { objects: root.source ? [root.source] : [] }
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -2,7 +2,7 @@ import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -174,7 +174,7 @@ Item {
onRunningChanged: if (!running) root.refresh()
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -186,7 +186,7 @@ Item {
}
}
Common.PopupCard {
PopupCard {
anchorItem: button
owner: root
bar: root.bar
@@ -222,7 +222,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
}
Common.Slider {
PanelSlider {
id: brightnessSlider
bar: root.bar
width: parent.width - 22 - brightnessPercent.width - 16
@@ -2,7 +2,7 @@ import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -66,7 +66,7 @@ Item {
// Single cursor model: exactly one highlighted spot across the whole
// panel, located via `focusSection` + (`selectedIndex` | `dnsIndex`).
// Mouse hover and keyboard nav both mutate this state at the root; items
// never read containsMouse for visuals. See Common.CursorSurface for the
// never read containsMouse for visuals. See CursorSurface for the
// shared chrome (fill / border) shared by NetworkRow and DnsProviderPill.
readonly property color activeFill: bar ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) : "transparent"
@@ -518,7 +518,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -535,12 +535,12 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
// Keyboard-driven popup anchored to the bar widget icon. The shared
// Common.KeyboardPanel handles the layer-shell PanelWindow scaffolding
// KeyboardPanel handles the layer-shell PanelWindow scaffolding
// (Exclusive focus on map, screen binding, anchored-to-icon positioning,
// outside-click via an overlay MouseArea + Region mask that lets the bar
// remain clickable, fade animation, popout coordination). What stays
// here is the wifi-specific UI inside.
Common.KeyboardPanel {
KeyboardPanel {
id: panel
anchorItem: button
owner: root
@@ -664,7 +664,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
}
Common.PillButton {
PillButton {
id: refreshBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -681,7 +681,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
}
Common.PanelSeparator {
PanelSeparator {
visible: !!root.info.iface
foreground: root.bar.foreground
}
@@ -776,7 +776,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
// DNS provider selection.
Common.PanelSeparator {
PanelSeparator {
foreground: root.bar.foreground
}
@@ -784,7 +784,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
width: parent.width
spacing: 8
Common.PanelSectionHeader {
PanelSectionHeader {
text: "DNS provider"
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
@@ -825,12 +825,12 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
}
// Wi-Fi networks (only if a Wi-Fi station is available).
Common.PanelSeparator {
PanelSeparator {
visible: root.wifiStationAvailable
foreground: root.bar.foreground
}
Common.PanelSectionHeader {
PanelSectionHeader {
visible: root.wifiStationAvailable
text: root.scanning ? "Scanning Wi-Fi…" : "Wi-Fi networks"
foreground: root.bar.foreground
@@ -882,7 +882,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
// One DNS provider pill. The cursor + current visuals come entirely from
// CursorSurface; this component just binds them to the panel's cursor
// state and renders the label/tooltip/click target.
component DnsProviderPill: Common.CursorSurface {
component DnsProviderPill: CursorSurface {
id: pill
required property string provider
required property int index
@@ -921,7 +921,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
onClicked: pill.clicked()
}
Common.PanelToolTip {
PanelToolTip {
visible: pill.tooltipText !== "" && pillMouse.containsMouse
text: pill.tooltipText
panelForeground: root.bar.foreground
@@ -934,7 +934,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
// expands inline to a passphrase prompt when the user picks a protected
// network we don't have credentials for. Clicking a connected row
// disconnects; the X button on a connected row forgets the network.
component NetworkRow: Common.CursorSurface {
component NetworkRow: CursorSurface {
id: row
required property var net
required property int index
@@ -1042,7 +1042,7 @@ iwctl known-networks list 2>/dev/null \\
anchors.verticalCenter: parent.verticalCenter
}
Common.PanelActionButton {
PanelActionButton {
id: forgetBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -1167,7 +1167,7 @@ iwctl known-networks list 2>/dev/null \\
// 22×22 right-anchored to line up with forgetBtn and lockIndicator
// above. Esc closes the prompt (handled by pwField.Keys.onEscapePressed)
// so there's no separate cancel button.
Common.PanelActionButton {
PanelActionButton {
id: connectPwBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -2,7 +2,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -72,7 +72,7 @@ Item {
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -103,7 +103,7 @@ Item {
}
}
Common.PopupCard {
PopupCard {
id: popup
anchorItem: button
bar: root.bar
@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -147,7 +147,7 @@ Item {
onButtonHoveredChanged: buttonHovered ? showPopup() : scheduleHide()
onPopupHoveredChanged: popupHovered ? hideTimer.stop() : scheduleHide()
Common.WidgetButton {
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
@@ -169,7 +169,7 @@ Item {
onHoveredChanged: root.buttonHovered = hovered
}
Common.PopupCard {
PopupCard {
id: popup
anchorItem: button
owner: root
@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import "../common" as Common
import qs.Ui
Item {
id: root
@@ -279,7 +279,7 @@ Item {
onTriggered: root.refresh()
}
Common.WidgetButton {
WidgetButton {
id: button
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
@@ -305,7 +305,7 @@ Item {
}
}
Common.PopupCard {
PopupCard {
id: popup
anchorItem: button
owner: root
@@ -1,7 +1,7 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import "../bar/common" as Common
import qs.Ui
import qs.Commons
// Visual reference + live playground for omarchy-shell's common UI
@@ -124,7 +124,7 @@ Item {
font.bold: true
}
Text {
text: "Live previews of every component in plugins/bar/common/. Use this as the visual reference when porting panels or extracting new primitives. Press h/l to walk the cursor demo, Esc to close."
text: "Live previews of every type exported from qs.Ui. Use this as the visual reference when porting panels or building plugins. Press h/l to walk the cursor demo, Esc to close."
color: Qt.darker(root.foreground, 1.4)
font.family: root.fontFamily
font.pixelSize: 11
@@ -133,7 +133,7 @@ Item {
}
}
Common.PanelSeparator { foreground: root.foreground }
PanelSeparator { foreground: root.foreground }
// ---- PanelSectionHeader ------------------------------------------
Column {
@@ -171,17 +171,17 @@ Item {
anchors.rightMargin: 14
spacing: 6
Common.PanelSectionHeader {
PanelSectionHeader {
text: "DNS provider"
foreground: root.foreground
fontFamily: root.fontFamily
}
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Wi-Fi networks"
foreground: root.foreground
fontFamily: root.fontFamily
}
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Playing"
foreground: root.foreground
fontFamily: root.fontFamily
@@ -227,9 +227,9 @@ Item {
anchors.rightMargin: 14
spacing: 12
Common.PanelSeparator { foreground: root.foreground }
Common.PanelSeparator { foreground: root.foreground; strength: 0.25 }
Common.PanelSeparator { foreground: root.foreground; strength: 0.45 }
PanelSeparator { foreground: root.foreground }
PanelSeparator { foreground: root.foreground; strength: 0.25 }
PanelSeparator { foreground: root.foreground; strength: 0.45 }
}
}
}
@@ -279,7 +279,7 @@ Item {
{ "label": "Forget / scan / disabled-ish row" }
]
Common.CursorSurface {
CursorSurface {
required property var modelData
required property int index
width: parent.width
@@ -351,7 +351,7 @@ Item {
anchors.leftMargin: 14
spacing: 6
Common.PillButton {
PillButton {
text: "DHCP"
tooltipText: "Use DNS from DHCP"
tooltipBackground: root.background
@@ -360,7 +360,7 @@ Item {
fontFamily: root.fontFamily
}
Common.PillButton {
PillButton {
text: "Cloudflare"
tooltipText: "Set DNS to Cloudflare"
tooltipBackground: root.background
@@ -370,7 +370,7 @@ Item {
active: true
}
Common.PillButton {
PillButton {
iconText: "󰑐"
tooltipText: "Refresh"
tooltipBackground: root.background
@@ -381,7 +381,7 @@ Item {
verticalPadding: 4
}
Common.PillButton {
PillButton {
iconText: "󰂯"
text: "On"
tooltipText: "Turn Bluetooth off"
@@ -431,7 +431,7 @@ Item {
anchors.leftMargin: 14
spacing: 18
Common.PanelActionButton {
PanelActionButton {
iconText: "󰄬"
tooltipText: "Confirm (default flavor)"
foreground: root.foreground
@@ -439,7 +439,7 @@ Item {
fontFamily: root.fontFamily
}
Common.PanelActionButton {
PanelActionButton {
iconText: "󰅙"
tooltipText: "Forget network (urgent flavor)"
foreground: root.foreground
@@ -448,7 +448,7 @@ Item {
fontFamily: root.fontFamily
}
Common.PanelActionButton {
PanelActionButton {
iconText: "󰄬"
tooltipText: "Disabled — type a passphrase first"
foreground: root.foreground
@@ -504,7 +504,7 @@ Item {
cursorShape: Qt.PointingHandCursor
}
Common.PanelToolTip {
PanelToolTip {
visible: tipMouse.containsMouse
text: "Styled tooltip — drop into any panel"
panelForeground: root.foreground
@@ -563,7 +563,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
}
Common.Slider {
PanelSlider {
id: demoSlider
bar: root.fakeBar
width: parent.width - 70
@@ -623,13 +623,13 @@ Item {
anchors.rightMargin: 14
spacing: 6
Common.PanelSectionHeader {
PanelSectionHeader {
text: "Wi-Fi networks"
foreground: root.foreground
fontFamily: root.fontFamily
}
Common.CursorSurface {
CursorSurface {
width: parent.width
implicitHeight: composedRow.implicitHeight + 12
current: true
@@ -655,7 +655,7 @@ Item {
font.pixelSize: 14
}
Common.PanelActionButton {
PanelActionButton {
id: composedForget
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -695,9 +695,9 @@ Item {
}
}
Common.PanelSeparator { foreground: root.foreground }
PanelSeparator { foreground: root.foreground }
Common.CursorSurface {
CursorSurface {
width: parent.width
implicitHeight: idleRow.implicitHeight + 12
foreground: root.foreground