diff options
Diffstat (limited to 'desktop')
| -rw-r--r-- | desktop/island/ControlTile.qml | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/desktop/island/ControlTile.qml b/desktop/island/ControlTile.qml new file mode 100644 index 0000000..873a42e --- /dev/null +++ b/desktop/island/ControlTile.qml @@ -0,0 +1,143 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import Quickshell +import Quickshell.Widgets +import Quickshell.Networking +import Quickshell.Bluetooth +import QtQuick +import ".." +import "Controls.js" as Controls +import "Logic.js" as Logic + +// One control: icon over label. Left-click toggles, right-click opens the +// drawer page that holds the rest, scroll changes volume on the two audio +// tiles. Toggle tiles fill when on; audio tiles fill to their volume. +Rectangle { + id: tile + + required property string tileId + // The sound module's Service. + required property QtObject sound + + signal openPage(string page) + + readonly property var info: Controls.tile(tileId) + readonly property bool isAudio: tileId === "output" || tileId === "mic" + readonly property var audio: tileId === "output" ? (sound.sink?.audio ?? null) + : tileId === "mic" ? (sound.source?.audio ?? null) + : null + readonly property var adapter: Bluetooth.defaultAdapter + // Networking starts empty for about two seconds (AGENTS.md). + readonly property bool wifiReady: + (Networking.devices?.values ?? []).some(d => d.type === DeviceType.Wifi) + + readonly property bool ready: isAudio ? audio !== null + : tileId === "bluetooth" ? adapter !== null + : tileId === "wifi" ? wifiReady + : true + + readonly property bool on: { + switch (tileId) { + case "wifi": return Networking.wifiEnabled; + case "bluetooth": return adapter?.enabled ?? false; + case "dnd": return Status.dnd; + // Lit while a game holds it too; a click then clears only the manual + // half (Status.qml), so the tile stays lit until the game ends. + case "presentation": return Status.presentation; + case "nolock": return !Status.nolock; + default: return audio ? !audio.muted : false; + } + } + + function toggle() { + switch (tileId) { + case "wifi": Networking.wifiEnabled = !Networking.wifiEnabled; break; + case "bluetooth": adapter.enabled = !adapter.enabled; break; + case "dnd": + case "presentation": + case "nolock": Status.toggleMode(tileId); break; + default: audio.muted = !audio.muted; + } + } + + radius: 12 + color: Qt.alpha(Theme.surface, 0.5) + border.width: 1 + border.color: Qt.alpha(Theme.text, 0.08) + opacity: ready ? 1 : 0.4 + clip: true + + // Toggle tiles: lit when on. + Rectangle { + anchors.fill: parent + radius: parent.radius + visible: !tile.isAudio && tile.on + color: Qt.alpha(Theme.accent, 0.35) + } + + // Audio tiles: a bar to the volume. Muted keeps the width in a neutral + // colour, so the level stays readable. Not the accent as the neutral: + // udt can snap it onto a palette colour (AGENTS.md). + Rectangle { + visible: tile.isAudio + anchors { left: parent.left; top: parent.top; bottom: parent.bottom } + width: parent.width * Controls.fill(tile.audio?.volume) + radius: parent.radius + color: tile.audio?.muted ? Qt.alpha(Theme.overlay, 0.6) : Qt.alpha(Theme.accent, 0.35) + } + + Column { + anchors.centerIn: parent + width: parent.width - 8 + spacing: 4 + + IconImage { + anchors.horizontalCenter: parent.horizontalCenter + implicitSize: 22 + source: Quickshell.iconPath(tile.tileId === "output" + ? Logic.volumeIcon(tile.audio?.volume ?? 0, tile.audio?.muted ?? false) + : Controls.icon(tile.tileId, tile.on), true) + } + + Text { + width: parent.width + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + text: tile.info?.label ?? tile.tileId + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.text + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + cursorShape: Qt.PointingHandCursor + onClicked: mouse => { + if (mouse.button === Qt.RightButton) tile.openPage(tile.info.page); + else if (tile.ready) tile.toggle(); + } + // One step per wheel notch (120), as the layout flag does. A + // MouseArea, not a WheelHandler: the handler never fired in the + // island (AGENTS.md). + property real acc: 0 + onWheel: wheel => { + wheel.accepted = true; + if (!tile.isAudio || !tile.ready) return; + acc += wheel.angleDelta.y; + if (Math.abs(acc) < 120) return; + const max = tile.tileId === "output" ? 1.5 : 1.0; + tile.audio.volume = Controls.step(tile.audio.volume, acc > 0 ? 0.05 : -0.05, max); + acc = 0; + } + } +} |
