Use component-like naming without a repeating namespace

This commit is contained in:
David Heinemeier Hansson
2026-05-25 14:32:41 +02:00
parent 829c1fa4f7
commit e6d990e31d
24 changed files with 92 additions and 92 deletions
@@ -6,7 +6,7 @@ import Quickshell.Services.Mpris
import Quickshell.Services.Pipewire
import qs.Ui
import qs.Commons
import "AudioModel.js" as AudioModel
import "Model.js" as Model
Panel {
id: root
@@ -62,11 +62,11 @@ Panel {
// playback streams consistently accept audio input from clients and publish
// `isSink: true`; capture streams publish as stream sources.
function isPlaybackStream(node) {
return AudioModel.isPlaybackStream(node)
return Model.isPlaybackStream(node)
}
function isAudioSource(node) {
return AudioModel.isAudioSource(node)
return Model.isAudioSource(node)
}
property var cachedAudioSinks: []
@@ -263,7 +263,7 @@ Panel {
onAudioStreamsChanged: scheduleDisplayAudioModelRefresh()
function listSnapshot(list) {
return AudioModel.listSnapshot(list)
return Model.listSnapshot(list)
}
function refreshDisplayAudioModels() {
@@ -354,7 +354,7 @@ Panel {
// panel's brightnessName ladder; bands are wide enough that small
// tweaks don't rename the room you're in.
function outputVolumeName(volume, muted) {
return AudioModel.outputVolumeName(volume, muted)
return Model.outputVolumeName(volume, muted)
}
function setOutputVolume(v) {
@@ -406,83 +406,83 @@ Panel {
}
function updateSinkAvailability(raw) {
sinkAvailability = AudioModel.parseSinkAvailability(raw)
sinkAvailability = Model.parseSinkAvailability(raw)
sinkAvailabilityLoaded = true
}
function friendlyDeviceLabel(text) {
return AudioModel.friendlyDeviceLabel(text)
return Model.friendlyDeviceLabel(text)
}
function nodeLabel(node) {
return AudioModel.nodeLabel(node)
return Model.nodeLabel(node)
}
function nodeProps(node) {
return AudioModel.nodeProps(node)
return Model.nodeProps(node)
}
function isHeadphones(node) {
return AudioModel.isHeadphones(node)
return Model.isHeadphones(node)
}
function sinkGlyph(node) {
return AudioModel.sinkGlyph(node)
return Model.sinkGlyph(node)
}
function sourceGlyph(node) {
return AudioModel.sourceGlyph(node)
return Model.sourceGlyph(node)
}
function friendlyStreamLabel(label) {
return AudioModel.friendlyStreamLabel(label)
return Model.friendlyStreamLabel(label)
}
function streamLabelKey(label) {
return AudioModel.streamLabelKey(label)
return Model.streamLabelKey(label)
}
function streamLabelIsGeneric(label) {
return AudioModel.streamLabelIsGeneric(label)
return Model.streamLabelIsGeneric(label)
}
function rawStreamLabel(node) {
return AudioModel.rawStreamLabel(node)
return Model.rawStreamLabel(node)
}
function mprisPlayerLabel(player) {
return AudioModel.mprisPlayerLabel(player)
return Model.mprisPlayerLabel(player)
}
function mprisPlayerIsProxy(player) {
return AudioModel.mprisPlayerIsProxy(player)
return Model.mprisPlayerIsProxy(player)
}
function streamRepresentsMprisPlayer(streamLabel, playerLabel) {
return AudioModel.streamRepresentsMprisPlayer(streamLabel, playerLabel)
return Model.streamRepresentsMprisPlayer(streamLabel, playerLabel)
}
function mprisLabelsFor(predicate) {
return AudioModel.mprisLabelsFor(mprisPlayers, predicate)
return Model.mprisLabelsFor(mprisPlayers, predicate)
}
function matchingMprisStreamLabel(label) {
return AudioModel.matchingMprisStreamLabel(label, mprisPlayers)
return Model.matchingMprisStreamLabel(label, mprisPlayers)
}
function unmatchedMprisStreamLabel(label) {
// Spotify exposes its PipeWire stream as "audio-src". For generic stream
// names, use the one MPRIS player not already represented by another audio
// stream (e.g. Chromium, or ALSA apps like cliamp).
return AudioModel.unmatchedMprisStreamLabel(label, mprisPlayers, displayAudioStreams)
return Model.unmatchedMprisStreamLabel(label, mprisPlayers, displayAudioStreams)
}
function streamLabel(node) {
return AudioModel.streamLabel(node, mprisPlayers, displayAudioStreams)
return Model.streamLabel(node, mprisPlayers, displayAudioStreams)
}
function streamRepresentsPlayer(node, player) {
return AudioModel.streamRepresentsPlayer(node, player, mprisPlayers, displayAudioStreams)
return Model.streamRepresentsPlayer(node, player, mprisPlayers, displayAudioStreams)
}
implicitWidth: button.implicitWidth
+1 -1
View File
@@ -9,7 +9,7 @@
"bar-widget"
],
"entryPoints": {
"barWidget": "BarWidget.qml"
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Audio",
@@ -5,7 +5,7 @@ import Quickshell.Io
import Quickshell.Bluetooth
import qs.Ui
import qs.Commons
import "BluetoothModel.js" as BluetoothModel
import "Model.js" as Model
Panel {
id: root
@@ -21,22 +21,22 @@ Panel {
readonly property var devices: Bluetooth.devices ? Bluetooth.devices.values : []
function deviceLabel(device) {
return BluetoothModel.deviceLabel(device)
return Model.deviceLabel(device)
}
function isUuidLike(value) {
return BluetoothModel.isUuidLike(value)
return Model.isUuidLike(value)
}
function isAddressLike(value) {
return BluetoothModel.isAddressLike(value)
return Model.isAddressLike(value)
}
function hasHumanName(device) {
return BluetoothModel.hasHumanName(device)
return Model.hasHumanName(device)
}
readonly property var deviceGroups: BluetoothModel.deviceLists(devices)
readonly property var deviceGroups: Model.deviceLists(devices)
readonly property var connectedDevices: deviceGroups.connected || []
readonly property var knownDevices: deviceGroups.known || []
readonly property var discoveredDevices: deviceGroups.discovered || []
@@ -105,11 +105,11 @@ Panel {
}
readonly property var visibleSections: {
return BluetoothModel.visibleSections(deviceGroups, adapter && adapter.discovering)
return Model.visibleSections(deviceGroups, adapter && adapter.discovering)
}
function devicesForSection(section) {
return BluetoothModel.sectionDevices(deviceGroups, section)
return Model.sectionDevices(deviceGroups, section)
}
function deviceAt(section, index) {
@@ -118,16 +118,16 @@ Panel {
}
function cloneMap(map) {
return BluetoothModel.cloneMap(map)
return Model.cloneMap(map)
}
function pendingAction(address) {
return BluetoothModel.pendingAction(pendingActions, address)
return Model.pendingAction(pendingActions, address)
}
function setPendingAction(address, action) {
if (!address) return
pendingActions = BluetoothModel.withPendingAction(pendingActions, address, action)
pendingActions = Model.withPendingAction(pendingActions, address, action)
if (action) pendingTimeout.restart()
}
+1 -1
View File
@@ -9,7 +9,7 @@
"bar-widget"
],
"entryPoints": {
"barWidget": "BarWidget.qml"
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Bluetooth",
+1 -1
View File
@@ -407,7 +407,7 @@ Item {
color: Qt.darker(root.foreground, 1.4)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/audio/BarWidget.qml for the canonical recipe."
text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/audio/Panel.qml for the canonical recipe."
}
Text {
width: parent.width
@@ -4,7 +4,7 @@ import Quickshell
import Quickshell.Io
import qs.Ui
import qs.Commons
import "MonitorModel.js" as MonitorModel
import "Model.js" as Model
Panel {
id: root
@@ -203,7 +203,7 @@ Panel {
}
function setBrightness(value) {
var percent = MonitorModel.clampBrightness(value)
var percent = Model.clampBrightness(value)
root.brightnessPercent = percent
root.pendingBrightnessPercent = percent
@@ -218,23 +218,23 @@ Panel {
}
function previewBrightness(value) {
root.brightnessPercent = MonitorModel.clampBrightness(value)
root.brightnessPercent = Model.clampBrightness(value)
brightnessDebounce.restart()
}
function normalizeScale(scale) {
return MonitorModel.normalizeScale(scale)
return Model.normalizeScale(scale)
}
// Playful mood-name for a given brightness percent. Bands intentionally
// span ~1020 points so casual tweaks change the label, while small
// nudges within one band don't.
function brightnessName(percent) {
return MonitorModel.brightnessName(percent)
return Model.brightnessName(percent)
}
function updateDisplays(displaysJson) {
var parsed = MonitorModel.parseDisplays(displaysJson)
var parsed = Model.parseDisplays(displaysJson)
root.displays = parsed.displays
root.enabledDisplayCount = parsed.enabledDisplayCount
}
+1 -1
View File
@@ -9,7 +9,7 @@
"bar-widget"
],
"entryPoints": {
"barWidget": "BarWidget.qml"
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Display",
@@ -5,7 +5,7 @@ import Quickshell.Io
import Quickshell.Networking
import qs.Ui
import qs.Commons
import "NetworkModel.js" as NetworkModel
import "Model.js" as Model
Panel {
id: root
@@ -229,7 +229,7 @@ Panel {
property string frequency: ""
function updateNetwork(raw) {
var parsed = NetworkModel.parseNetworkStatus(raw)
var parsed = Model.parseNetworkStatus(raw)
kind = parsed.kind
label = parsed.label
signalStrength = parsed.signalStrength
@@ -241,7 +241,7 @@ Panel {
Quickshell.execDetached(["bash", "-lc", "printf %s " + Util.shellQuote(value) + " | wl-copy"])
}
readonly property string icon: NetworkModel.connectionIcon(kind, signalStrength)
readonly property string icon: Model.connectionIcon(kind, signalStrength)
function refresh(scanWifi) {
if (scanWifi === undefined) scanWifi = false
@@ -263,25 +263,25 @@ Panel {
}
function formatHeaderSpeed(mbps) {
return NetworkModel.formatHeaderSpeed(mbps)
return Model.formatHeaderSpeed(mbps)
}
function formatHeaderFreq(mhz) {
return NetworkModel.formatHeaderFreq(mhz)
return Model.formatHeaderFreq(mhz)
}
function headerDetail() {
return NetworkModel.headerDetail(info)
return Model.headerDetail(info)
}
function updateDetails(raw) {
var next = NetworkModel.parseKeyValue(raw)
var next = Model.parseKeyValue(raw)
info = next
updateThroughput(next)
}
function updateThroughput(next) {
var state = NetworkModel.throughputState({
var state = Model.throughputState({
prevIface: prevIface,
prevRxBytes: prevRxBytes,
prevTxBytes: prevTxBytes,
@@ -299,11 +299,11 @@ Panel {
}
function formatBytes(bytes) {
return NetworkModel.formatBytes(bytes)
return Model.formatBytes(bytes)
}
function formatRate(bytesPerSec) {
return NetworkModel.formatRate(bytesPerSec)
return Model.formatRate(bytesPerSec)
}
function findDevice(type) {
@@ -330,20 +330,20 @@ Panel {
var network = networks[i]
if (!network) continue
checkActionCompletion(network)
var row = NetworkModel.wifiRow(network)
var row = Model.wifiRow(network)
if (row) nets.push(row)
}
wifiNetworks = NetworkModel.sortWifiRows(nets)
wifiNetworks = Model.sortWifiRows(nets)
wifiStationAvailable = !!wifiDevice
scanning = false
}
function wifiSectionTitle(index) {
return NetworkModel.wifiSectionTitle(wifiNetworks, index)
return Model.wifiSectionTitle(wifiNetworks, index)
}
function wifiIconFor(strength) {
return NetworkModel.wifiIconFor(strength)
return Model.wifiIconFor(strength)
}
function updateDns(raw) {
@@ -374,7 +374,7 @@ Panel {
}
function isProtected(security) {
return NetworkModel.isProtected(security, WifiSecurityType.Open)
return Model.isProtected(security, WifiSecurityType.Open)
}
function openPasswordPrompt(ssid) {
@@ -432,7 +432,7 @@ Panel {
}
function networkFailureReason(reason) {
return NetworkModel.networkFailureReason(reason, {
return Model.networkFailureReason(reason, {
NoSecrets: ConnectionFailReason.NoSecrets,
WifiAuthTimeout: ConnectionFailReason.WifiAuthTimeout,
WifiNetworkLost: ConnectionFailReason.WifiNetworkLost,
+1 -1
View File
@@ -9,7 +9,7 @@
"bar-widget"
],
"entryPoints": {
"barWidget": "BarWidget.qml"
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Network",
@@ -4,7 +4,7 @@ import Quickshell.Io
import Quickshell.Services.UPower
import qs.Commons
import qs.Ui
import "PowerModel.js" as PowerModel
import "Model.js" as Model
Panel {
id: root
@@ -31,7 +31,7 @@ Panel {
}
function selectProfileByDelta(delta) {
profileIndex = PowerModel.selectProfileIndex(profileIndex, delta, profiles)
profileIndex = Model.selectProfileIndex(profileIndex, delta, profiles)
}
function activateSelectedProfile() {
@@ -41,16 +41,16 @@ Panel {
function batteryIcon() {
var device = UPower.displayDevice
return PowerModel.batteryIcon(device, UPower.onBattery, upowerStates())
return Model.batteryIcon(device, UPower.onBattery, upowerStates())
}
function modeLabel() {
var device = UPower.displayDevice
return PowerModel.modeLabel(device, UPower.onBattery, upowerStates())
return Model.modeLabel(device, UPower.onBattery, upowerStates())
}
function profileIcon(name) {
return PowerModel.profileIcon(name)
return Model.profileIcon(name)
}
readonly property bool fullyCharged: {
@@ -59,7 +59,7 @@ Panel {
}
readonly property bool chargeThresholdActive: {
var device = UPower.displayDevice
return PowerModel.chargeThresholdActive(device, UPower.onBattery, upowerStates())
return Model.chargeThresholdActive(device, UPower.onBattery, upowerStates())
}
readonly property bool batteryFull: fullyCharged || (!UPower.onBattery && batteryFraction >= 1)
readonly property bool batteryFlowIdle: batteryFull || chargeThresholdActive
@@ -67,7 +67,7 @@ Panel {
// 0..1 charge level, used by the visual progress bar.
readonly property real batteryFraction: {
var d = UPower.displayDevice
return PowerModel.batteryFraction(d)
return Model.batteryFraction(d)
}
readonly property bool batteryLow: UPower.onBattery && batteryFraction > 0 && batteryFraction <= 0.2
@@ -131,7 +131,7 @@ Panel {
}
function updateKeyValue(raw, targetName) {
var next = PowerModel.parseKeyValue(raw)
var next = Model.parseKeyValue(raw)
// Keep last known good data if a refresh briefly returns nothing happens
// around AC plug/unplug events. Avoids the section collapsing mid-transition.
if (Object.keys(next).length === 0) return
@@ -140,7 +140,7 @@ Panel {
}
function updateProfiles(raw) {
var parsed = PowerModel.parseProfiles(raw, profileIndex)
var parsed = Model.parseProfiles(raw, profileIndex)
// Same guard as battery: preserve the last known profile list across
// transient empty payloads so the buttons don't blink out.
if (parsed.profiles.length === 0) return
+1 -1
View File
@@ -9,7 +9,7 @@
"bar-widget"
],
"entryPoints": {
"barWidget": "BarWidget.qml"
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Power",
+14 -14
View File
@@ -3,7 +3,7 @@ import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Ui
import "WeatherModel.js" as WeatherModel
import "Model.js" as Model
Panel {
id: root
@@ -32,7 +32,7 @@ Panel {
property string klass: ""
function updateWeather(raw) {
var data = WeatherModel.parseWeatherStatus(raw)
var data = Model.parseWeatherStatus(raw)
label = data.label
klass = data.klass
}
@@ -83,54 +83,54 @@ Panel {
}
function buildForecastDays() {
return WeatherModel.buildForecastDays(report, dailyForecastReport, Qt.formatDate(new Date(), "yyyy-MM-dd"))
return Model.buildForecastDays(report, dailyForecastReport, Qt.formatDate(new Date(), "yyyy-MM-dd"))
}
function openMeteoForecastDays() {
return WeatherModel.openMeteoForecastDays(dailyForecastReport, Qt.formatDate(new Date(), "yyyy-MM-dd"))
return Model.openMeteoForecastDays(dailyForecastReport, Qt.formatDate(new Date(), "yyyy-MM-dd"))
}
function wttrNextForecastDays() {
return WeatherModel.wttrNextForecastDays(report, Qt.formatDate(new Date(), "yyyy-MM-dd"))
return Model.wttrNextForecastDays(report, Qt.formatDate(new Date(), "yyyy-MM-dd"))
}
function isFutureForecastDate(dateString) {
return WeatherModel.isFutureForecastDate(dateString, Qt.formatDate(new Date(), "yyyy-MM-dd"))
return Model.isFutureForecastDate(dateString, Qt.formatDate(new Date(), "yyyy-MM-dd"))
}
function roundedTemp(value) {
return WeatherModel.roundedTemp(value)
return Model.roundedTemp(value)
}
function celsiusToFahrenheit(value) {
return WeatherModel.celsiusToFahrenheit(value)
return Model.celsiusToFahrenheit(value)
}
function formatTemp(value) {
return WeatherModel.formatTemp(value, useImperial)
return Model.formatTemp(value, useImperial)
}
function dayName(dateString) {
return WeatherModel.dayName(dateString, function(date) { return Qt.formatDate(date, "dddd") })
return Model.dayName(dateString, function(date) { return Qt.formatDate(date, "dddd") })
}
// Bare degree value (no unit letter), used in the forecast row.
function bareTempForDay(day, kind) {
return WeatherModel.bareTempForDay(day, kind, useImperial)
return Model.bareTempForDay(day, kind, useImperial)
}
// Representative icon for a forecast day: the hourly entry nearest noon.
function dayIcon(day) {
return WeatherModel.dayIcon(day)
return Model.dayIcon(day)
}
function iconForOpenMeteoCode(code) {
return WeatherModel.iconForOpenMeteoCode(code)
return Model.iconForOpenMeteoCode(code)
}
// Mirrors omarchy-weather-icon's wttr.in code → nerd-font glyph mapping.
function iconForCode(code, night) {
return WeatherModel.iconForCode(code, night)
return Model.iconForCode(code, night)
}
Process {