Initial commit

remove syncthing folder

bar/popops: fix menu window anims and positioning

bar/popops: change anims a little and add dropshadow

Update README.md

widgets/coloredicon: move to colorization, looks worse but..., yea

bar/popops: make popup window dissapear on menu close

README: add todo list, and brief desc

Update README.md

Update README.md

Update README.md

bar/systray: issue recreate on interact

bar/systray: hide popup on interact

bar/systray: add arrow for entries with children

bar/battery: start of battery widget

wallpaper/matugen: add foot template

extra sizing conditions for sys tray

bar/systray: add some more margin to text

update settings schema

bar/workspaces: filter by monitor, switch to scriptmodel

settings: fix settings lol

bar/systray: fix right item

feat: screenshot tool

clipboard one day...

feat: init lockscreen

mpris: add ipc handler for multimedia keys

mpris stuff

save progress

put shell in subdir, and add nix package

move readme back woops

bar/volume: make tool bar smaller

greeter: init greeter

greeter: fixed resource links

readme: update checklist

progress maybe, maybe not

fix: fixed screenshot tool not working

fix: bar layout issues

progress save

progress update

track styled popup

still broken but getting there

still broken but getting there

fix: gitignore qmlls.ini

fix: remove qmlls.ini

progress save

new popup system

new popup system

new popup system

more work on popups

fix: mask issues on popups

update readme
This commit is contained in:
kossLAN 2025-06-07 04:01:14 -04:00
commit 9e44812e93
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
102 changed files with 4592 additions and 0 deletions

View file

@ -0,0 +1,12 @@
import QtQuick
import Quickshell.Wayland
import ".."
Text {
id: windowText
text: ToplevelManager.activeToplevel?.title ?? ""
color: ShellSettings.colors.active
font.pointSize: 11
visible: text !== ""
elide: Text.ElideRight
}

101
shell/bar/Bar.qml Normal file
View file

@ -0,0 +1,101 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import "power"
// import "volume"
import "systray"
// import qs.widgets
import qs
Variants {
model: Quickshell.screens
delegate: PanelWindow {
id: root
color: ShellSettings.colors.surface_translucent
implicitHeight: ShellSettings.sizing.barHeight
screen: modelData
required property var modelData
anchors {
top: true
left: true
right: true
}
readonly property Popup popup: Popup {
bar: root
}
RowLayout {
spacing: 0
anchors {
fill: parent
leftMargin: 5
rightMargin: 5
}
// Left side of bar
RowLayout {
spacing: 15
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignLeft
Workspaces {
screen: root.screen
Layout.fillHeight: true
}
ActiveWindow {
id: activeWindow
Layout.preferredWidth: 400
}
}
// PowerMenu {
// bar: root
// Layout.fillHeight: true
// }
// Right side of bar
RowLayout {
spacing: 10
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight
SysTray {
bar: root
Layout.fillHeight: true
}
// VolumeIndicator {
// id: volumeIndicator
// popup: root.popup
// Layout.preferredWidth: this.height
// Layout.fillHeight: true
// Layout.topMargin: 2
// Layout.bottomMargin: 2
// }
// PowerMenu {
// bar: root
// Layout.fillHeight: true
// }
// Widgets.Separator {
// Layout.leftMargin: 5
// Layout.rightMargin: 5
// }
Clock {
id: clock
color: ShellSettings.colors.active
}
}
}
}
}

21
shell/bar/Clock.qml Normal file
View file

@ -0,0 +1,21 @@
import QtQuick
import Quickshell
Text {
property string ap: sysClock.hours >= 12 ? "PM" : "AM"
property string minutes: sysClock.minutes.toString().padStart(2, '0')
property string hours: {
var value = sysClock.hours % 12;
if (value === 0)
return 12;
return value;
}
SystemClock {
id: sysClock
enabled: true
}
text: `${hours}:${minutes} ${ap}`
font.pointSize: 11
}

197
shell/bar/Popup.qml Normal file
View file

@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.widgets
Scope {
id: root
required property var bar
property real gaps: 5
property Item parentItem
property PopupItem activeItem
property PopupItem lastActiveItem
property PopupItem shownItem: activeItem ?? lastActiveItem
onActiveItemChanged: {
if (activeItem != null) {
activeItem.targetVisible = true;
if (parentItem) {
activeItem.parent = parentItem;
}
}
if (lastActiveItem != null && lastActiveItem != activeItem) {
lastActiveItem.targetVisible = false;
}
if (activeItem != null) {
lastActiveItem = activeItem;
}
}
function setItem(item: PopupItem) {
activeItem = item;
}
function removeItem(item: PopupItem) {
if (activeItem == item) {
activeItem = null;
}
}
function onHidden(item: PopupItem) {
if (item == lastActiveItem) {
console.log("triggered");
lastActiveItem = null;
}
}
property real scaleMul: lastActiveItem && lastActiveItem.targetVisible ? 1 : 0
Behavior on scaleMul {
SmoothedAnimation {
velocity: 5
}
}
LazyLoader {
id: popupLoader
activeAsync: root.shownItem != null
PopupWindow {
id: popup
visible: true
color: "transparent"
implicitWidth: root.bar.width
implicitHeight: Math.max(800, parentItem.targetHeight)
anchor {
window: root.bar
rect: Qt.rect(0, 0, root.bar.width, root.bar.height)
edges: Edges.Bottom | Edges.Left
gravity: Edges.Bottom | Edges.Right
adjustment: PopupAdjustment.None
}
mask: Region {
item: parentItem
}
HyprlandFocusGrab {
id: grab
active: true
windows: [popup, root.bar]
onCleared: {
if (!active) {
root.shownItem.closed();
}
}
}
// HyprlandWindow.opacity: root.scaleMul
HyprlandWindow.visibleMask: popup.mask
Connections {
target: root
function onScaleMulChanged() {
popup.mask.changed();
}
}
StyledRectangle {
id: parentItem
width: targetWidth
height: targetHeight
x: targetX
y: root.gaps
transform: Scale {
origin.x: parentItem.targetX
origin.y: 0
xScale: 1
yScale: root.scaleMul
}
readonly property var targetWidth: root.shownItem?.implicitWidth ?? 0
readonly property var targetHeight: root.shownItem?.implicitHeight ?? 0
readonly property var targetX: {
if (root.shownItem == null) {
return 0;
}
let owner = root.shownItem.owner;
let bar = root.bar;
let isCentered = root.shownItem.centered;
let xPos = owner.mapToItem(bar.contentItem, 0, bar.height, owner.width, 0).x;
let rightEdge = xPos + targetWidth;
let maxRightEdge = popup.width;
if (isCentered) {
return xPos - (targetWidth / 2) + (owner.width / 2);
}
if (rightEdge > maxRightEdge) {
// touching right edge, reposition
// console.log("touching right edge");
return maxRightEdge - targetWidth - root.gaps;
}
return xPos;
}
Component.onCompleted: {
root.parentItem = this;
if (root.activeItem) {
root.activeItem.parent = this;
}
}
// TODO: Make a close animation, a little complicated, will need to track if an animation is running
// and stop unload from occuring until its done, in the LazyLoader.
Behavior on x {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
Behavior on width {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
Behavior on height {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
// SmoothedAnimation on height {
// duration: 200
// easing.type: Easing.InOutQuad
// to: parentItem.targetHeight
// onToChanged: restart()
// }
}
}
}
}

66
shell/bar/PopupItem.qml Normal file
View file

@ -0,0 +1,66 @@
import QtQuick
import qs.widgets
Item {
id: root
visible: false
opacity: root.targetOpacity
onShowChanged: {
if (show) {
popup.setItem(this);
} else {
popup.removeItem(this);
}
}
onTargetVisibleChanged: {
if (targetVisible) {
visible = true;
targetOpacity = 1;
} else {
console.log("closed");
closed();
targetOpacity = 0;
}
}
onTargetOpacityChanged: {
if (!targetVisible && targetOpacity == 0) {
visible = false;
this.parent = null;
if (popup)
popup.onHidden(this);
}
}
readonly property alias contentItem: contentItem
default property alias data: contentItem.data
readonly property Item item: contentItem
Item {
id: contentItem
anchors.fill: parent
// anchors.margins: 5
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
}
required property var popup
required property var owner
property bool centered: false
property bool show: false
signal closed
property bool targetVisible: false
property real targetOpacity: 0
Behavior on targetOpacity {
id: opacityAnimation
SmoothedAnimation {
velocity: 5
}
}
}

72
shell/bar/Workspaces.qml Normal file
View file

@ -0,0 +1,72 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import qs
RowLayout {
spacing: 6
visible: Hyprland.monitors.values.length != 0
required property var screen
Repeater {
id: workspaceButtons
model: ScriptModel {
values: Hyprland.workspaces.values.slice().filter(workspace => workspace.monitor === Hyprland.monitorFor(screen))
}
Rectangle {
radius: height / 2
color: {
let value = ShellSettings.colors.active_translucent;
if (!modelData?.id || !Hyprland.focusedMonitor?.activeWorkspace?.id)
return value;
if (workspaceButton.containsMouse) {
value = ShellSettings.colors.highlight;
} else if (Hyprland.focusedMonitor.activeWorkspace.id === modelData.id) {
value = ShellSettings.colors.highlight;
}
return value;
}
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 12
Layout.preferredWidth: {
if (Hyprland.focusedMonitor?.activeWorkspace?.id === modelData?.id)
return 25;
return 12;
}
required property var modelData
Behavior on Layout.preferredWidth {
SmoothedAnimation {
duration: 150
velocity: 200
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 100
easing.type: Easing.OutQuad
}
}
MouseArea {
id: workspaceButton
anchors.fill: parent
hoverEnabled: true
onPressed: Hyprland.dispatch(`workspace ${parent.modelData.id}`)
}
}
}
}

View file

@ -0,0 +1,42 @@
import QtQuick
import "../../mpris" as Mpris
import "../../widgets" as Widgets
import "../.."
Widgets.MaterialButton {
id: root
radius: 6
implicitWidth: mediaInfo.contentWidth + 8
implicitHeight: parent.height
// onClicked: {
// popup.visible = !popup.visible;
// }
required property var bar
property var player: Mpris.Controller.trackedPlayer
Text {
id: mediaInfo
text: root.player?.trackTitle ?? ""
color: root.containsMouse ? ShellSettings.colors["inverse_primary"] : ShellSettings.colors["inverse_surface"]
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pointSize: 11
anchors.centerIn: parent
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
// WidgetWindow {
// id: popup
// visible: false
// parentWindow: root.bar
//
// // anchor.window: root.bar
// }

View file

@ -0,0 +1,9 @@
import Quickshell
PopupWindow {
id: root
color: "red"
implicitWidth: 500
implicitHeight: 500
}

View file

@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import "../.."
Item {
id: root
required property var bar
property var implicitSize: 0
readonly property real actualSize: Math.min(root.width, root.height)
implicitWidth: parent.height
implicitHeight: parent.height
NotificationCenter {
id: notificationCenter
}
Rectangle {
color: mouseArea.containsMouse ? ShellSettings.colors["primary"] : "transparent"
radius: 5
anchors {
fill: parent
margins: 1
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onPressed: {
if (notificationCenter.visible) {
notificationCenter.hide();
} else {
notificationCenter.show();
}
}
}
Item {
implicitWidth: root.implicitSize
implicitHeight: root.implicitSize
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: root.actualSize
height: root.actualSize
color: "white"
}
maskSource: IconImage {
implicitSize: root.actualSize
source: "root:resources/general/notification.svg"
}
}
Rectangle {
color: mouseArea.containsMouse ? ShellSettings.colors["inverse_primary"] : ShellSettings.colors["inverse_surface"]
anchors.fill: parent
}
}
// TODO: notification number overlay
}

View file

@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import Quickshell.Services.UPower
import qs.widgets
import qs.bar
import qs
// todo: redo the tray icon handling
StyledMouseArea {
id: root
implicitWidth: height + 8 // for margin
visible: UPower.displayDevice.isLaptopBattery
onClicked: showMenu = !showMenu
required property var bar
property bool showMenu: false
Item {
implicitWidth: parent.height
implicitHeight: parent.height
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: root.width
height: root.height
color: "white"
}
maskSource: IconImage {
implicitSize: root.width
source: "root:resources/battery/battery.svg"
}
}
Rectangle {
id: batteryBackground
color: Qt.color(ShellSettings.colors["surface"]).lighter(4)
opacity: 0.75
anchors {
fill: parent
margins: 2
}
}
Rectangle {
id: batteryPercentage
width: (parent.width - 4) * UPower.displayDevice.percentage
color: ShellSettings.colors["inverse_surface"]
anchors {
left: batteryBackground.left
top: batteryBackground.top
bottom: batteryBackground.bottom
}
}
}
property PopupItem menu: PopupItem {
owner: root
popup: root.bar.popup
show: root.showMenu
onClosed: root.showMenu = false
centered: true
implicitWidth: 250
implicitHeight: 250
}
}

View file

@ -0,0 +1,96 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import "../../widgets"
import ".."
// TODO:
// 1. Get rid of leftItem/rightItem properties on menu
// 2. Load menu properly, right now its pretty buggy
// 3. Fix bug that causes close on update (nm-applet wifi networks updating)
RowLayout {
id: root
spacing: 5
visible: SystemTray.items.values.length > 0
required property var bar
Repeater {
id: repeater
model: SystemTray.items
delegate: StyledMouseArea {
id: button
Layout.preferredWidth: parent.height
Layout.fillHeight: true
required property SystemTrayItem modelData
property bool showMenu: false
onClicked: {
menuOpener.menu = modelData.menu;
showMenu = !showMenu;
}
IconImage {
id: trayIcon
anchors.fill: parent
source: {
// console.log(trayField.modelData.id);
switch (button.modelData.id) {
case "obs":
return "image://icon/obs-tray";
default:
return button.modelData.icon;
}
}
}
QsMenuOpener {
id: menuOpener
}
property PopupItem menu: PopupItem {
id: menu
owner: button
popup: root.bar.popup
show: button.showMenu
onClosed: button.showMenu = false
implicitWidth: content.implicitWidth + (2 * 8)
implicitHeight: content.implicitHeight + (2 * 8)
property var leftItem: false
property var rightItem: false
ColumnLayout {
id: content
spacing: 2
anchors.centerIn: parent
Repeater {
model: menuOpener.children
delegate: TrayMenuItem {
id: sysTrayContent
Layout.fillWidth: true
Layout.fillHeight: true
rootMenu: menu
onInteracted: {
button.showMenu = false;
menuOpener.menu = null;
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,177 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../../widgets" as Widgets
import "../.."
ColumnLayout {
id: root
required property QsMenuEntry menuData
required property var rootMenu
signal interacted
Component.onCompleted: {
if (menuData?.buttonType !== QsMenuButtonType.None || menuData?.icon != "") {
rootMenu.leftItem = true;
}
if (menuData?.hasChildren) {
rootMenu.rightItem = true;
}
}
WrapperRectangle {
Layout.fillWidth: true
Layout.preferredHeight: 25
radius: 4
color: {
if (!root.menuData?.enabled)
return "transparent";
if (entryArea.containsMouse) {
return ShellSettings.colors.active_translucent;
}
return "transparent";
}
WrapperMouseArea {
id: entryArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
if (!root.menuData?.enabled)
return;
if (root.menuData?.hasChildren) {
subTrayMenu.visible = !subTrayMenu.visible;
return;
}
root.menuData?.triggered();
root.interacted();
}
RowLayout {
id: menuEntry
spacing: 5
Layout.fillWidth: true
Item {
visible: root.rootMenu.leftItem
Layout.preferredWidth: 20
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 5
RadioButton {
id: radioButton
visible: (root.menuData?.buttonType === QsMenuButtonType.RadioButton) ?? false
checked: (root.menuData?.checkState) ?? false
anchors.centerIn: parent
}
CheckBox {
id: checkBox
visible: (root.menuData?.buttonType === QsMenuButtonType.CheckBox) ?? false
checked: (root.menuData?.checkState) ?? false
anchors.centerIn: parent
}
IconImage {
id: entryImage
visible: (root.menuData?.buttonType === QsMenuButtonType.None && root.menuData?.icon !== "") ?? false
source: (root.menuData?.icon) ?? ""
anchors.fill: parent
}
}
Text {
id: text
text: root.menuData?.text ?? ""
verticalAlignment: Text.AlignVCenter
color: {
let color = Qt.color(ShellSettings.colors.active);
if (!root.menuData?.enabled)
return color.darker(2);
// if (entryArea.containsMouse)
// return Qt.color(ShellSettings.colors["inverse_primary"]);
return color;
}
Layout.fillWidth: true
Layout.fillHeight: true
}
Item {
visible: root.rootMenu.rightItem
Layout.preferredHeight: 20
Layout.preferredWidth: 20
Layout.rightMargin: 5
Widgets.IconButton {
id: arrowButton
visible: root.menuData?.hasChildren ?? false
activeRectangle: false
source: "root:resources/general/right-arrow.svg"
rotation: subTrayMenu.visible ? 90 : 0
anchors.fill: parent
Behavior on rotation {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
onClicked: {
root.expanded = !root.expanded;
}
}
}
}
}
}
WrapperRectangle {
id: subTrayMenu
color: ShellSettings.colors.surface_container_translucent
radius: 8
visible: false
border {
width: 1
color: ShellSettings.colors.active_translucent
}
Layout.fillWidth: true
QsMenuOpener {
id: menuOpener
menu: root.menuData
}
ColumnLayout {
id: subTrayContainer
spacing: 2
Layout.fillWidth: true
Repeater {
model: menuOpener.children
delegate: BoundComponent {
id: subMenuEntry
source: "TrayMenuItem.qml"
Layout.fillWidth: true
required property var modelData
property var rootMenu: root.rootMenu
}
}
}
}
}

View file

@ -0,0 +1,30 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs
ColumnLayout {
id: root
required property QsMenuEntry modelData
required property var rootMenu
property var leftItem
signal interacted
Rectangle {
visible: (root.modelData?.isSeparator ?? false)
color: ShellSettings.colors.inactive_translucent
// opacity: 0.1
Layout.fillWidth: true
Layout.preferredHeight: 2
Layout.leftMargin: 8
Layout.rightMargin: 8
}
TrayMenuEntry {
visible: !root.modelData?.isSeparator
rootMenu: root.rootMenu
menuData: root.modelData
Layout.fillWidth: true
onInteracted: root.interacted()
}
}

View file

@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
ColumnLayout {
id: root
Loader {
id: sinkLoader
active: sink
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: WrapperItem {
PwNodeLinkTracker {
id: linkTracker
node: sinkLoader.sink
}
ColumnLayout {
Repeater {
model: linkTracker.linkGroups
delegate: Loader {
id: nodeLoader
active: modelData.source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
required property PwLinkGroup modelData
sourceComponent: VolumeCard {
id: nodeCard
node: nodeLoader.modelData.source
text: node.properties["media.name"] ?? ""
// if icon-name is undefined, just gonna fallback on the application name
icon: IconImage {
source: {
if (nodeCard.node.properties["application.icon-name"] !== undefined)
return `image://icon/${nodeCard.node.properties["application.icon-name"]}`;
let applicationName = nodeCard.node.properties["application.name"];
return `image://icon/${applicationName?.toLowerCase() ?? "image-missing"}`;
}
}
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: nodeCard.node.audio.muted ? "volume_off" : "volume_up"
checked: !nodeCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
nodeCard.node.audio.muted = !nodeCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}
}
}
}
}

View file

@ -0,0 +1,64 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
ColumnLayout {
id: root
// headphones
// don't load until the node is not null
Loader {
id: sinkLoader
active: sink !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: VolumeCard {
id: sinkCard
node: sinkLoader.sink
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: sinkCard.node.audio.muted ? "volume_off" : "volume_up"
checked: !sinkCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
sinkCard.node.audio.muted = !sinkCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
// microphone, same as above
Loader {
id: sourceLoader
active: source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode source: Pipewire.defaultAudioSource
sourceComponent: VolumeCard {
id: sourceCard
node: sourceLoader.source
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: sourceCard.node.audio.muted ? "mic_off" : "mic"
checked: !sourceCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
sourceCard.node.audio.muted = !sourceCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}

View file

@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
WrapperRectangle {
id: root
color: ShellSettings.colors["surface_container"]
radius: width / 2
margin: 6
required property PwNode node
property string text
property Component button
property Component icon
PwObjectTracker {
id: tracker
objects: [root.node]
}
RowLayout {
Widgets.MaterialSlider {
value: root.node.audio.volume ?? 0
text: root.text
icon: root.icon
onValueChanged: {
// only allow changes when the node is ready other wise you will combust
if (!root.node.ready)
return;
root.node.audio.volume = value;
}
Layout.fillWidth: true
Layout.fillHeight: true
}
Loader {
id: buttonLoader
sourceComponent: root.button
Layout.preferredWidth: this.height
Layout.fillHeight: true
}
}
}

View file

@ -0,0 +1,34 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import "../../widgets/" as Widgets
WrapperItem {
id: root
visible: false
ColumnLayout {
spacing: 10
Widgets.TabBar {
id: tabBar
model: ["headphones", "tune"]
Layout.fillWidth: true
Layout.preferredHeight: 35
}
StackLayout {
id: page
currentIndex: tabBar.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: currentItem ? currentItem.implicitHeight : 0
readonly property Item currentItem: children[currentIndex]
DeviceMixer {}
ApplicationMixer {}
}
}
}

View file

@ -0,0 +1,27 @@
import QtQuick
import "../../widgets/" as Widgets
Item {
id: root
required property var popup
Widgets.FontIconButton {
id: button
iconName: "volume_up"
anchors.fill: parent
onClicked: {
if (root.popup.content == volumeMenu) {
root.popup.hide();
return;
}
root.popup.set(this, volumeMenu);
root.popup.show();
}
}
VolumeControl {
id: volumeMenu
}
}