diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/chevron-down.svg b/dots/.config/quickshell/ii/assets/icons/fluent/chevron-down.svg new file mode 100644 index 000000000..604bf9887 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml index e29c13a44..7f527c97d 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml @@ -75,7 +75,7 @@ BarButton { component BackgroundAcrylicRectangle: AcrylicRectangle { shiny: ((root.hovered && !root.down) || root.checked) - color: root.colBackground + color: root.color border.width: 1 border.color: root.colBackgroundBorder diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml index c98d57baf..f8192389a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml @@ -11,8 +11,11 @@ Button { property var altAction: () => {} property var middleClickAction: () => {} - property color colBackground + property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) + property color colBackgroundHover: Looks.colors.bg1Hover + property color colBackgroundActive: Looks.colors.bg1Active property color colBackgroundBorder + property color color Layout.fillHeight: true topInset: 4 bottomInset: 4 @@ -37,14 +40,14 @@ Button { } } - colBackgroundBorder: ColorUtils.transparentize(Looks.colors.bg1Border, root.checked ? Looks.contentTransparency : 1) - colBackground: { + colBackgroundBorder: ColorUtils.transparentize(Looks.colors.bg1Border, (root.checked || root.hovered) ? Looks.contentTransparency : 1) + color: { if (root.down) { - return Looks.colors.bg1Active + return root.colBackgroundActive } else if ((root.hovered && !root.down) || root.checked) { - return Looks.colors.bg1Hover + return root.colBackgroundHover } else { - return ColorUtils.transparentize(Looks.colors.bg1) + return root.colBackground } } @@ -66,7 +69,7 @@ Button { background: AcrylicRectangle { shiny: ((root.hovered && !root.down) || root.checked) - color: root.colBackground + color: root.color radius: Looks.radius.medium border.width: 1 border.color: root.colBackgroundBorder diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarIconButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarIconButton.qml new file mode 100644 index 000000000..5b4b327b6 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarIconButton.qml @@ -0,0 +1,40 @@ +import QtQuick +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.waffle.looks +import qs.modules.waffle.bar + +BarButton { + id: root + + property alias iconName: iconContent.icon + property alias iconSource: iconContent.source + property alias iconSize: iconContent.implicitSize + property alias iconRotation: iconContent.rotation + property alias iconMonochrome: iconContent.monochrome + property alias tooltipText: tooltip.text + property alias overlayingItems: iconContent.data + + implicitWidth: 32 + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: iconContent.implicitWidth + implicitHeight: iconContent.implicitHeight + + FluentIcon { + id: iconContent + anchors.centerIn: parent + implicitSize: 16 + icon: root.iconName + monochrome: false + } + } + + BarToolTip { + id: tooltip + extraVisibleCondition: root.shouldShowTooltip && text !== "" + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml index 512b3b2bd..f000c92b0 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml @@ -13,12 +13,23 @@ Loader { required property var contentItem property real padding: Looks.radius.large - Looks.radius.medium property bool noSmoothClosing: !Config.options.waffles.smootherAnimations + property bool closeOnFocusLost: true + signal focusCleared() property Item anchorItem: parent property real visualMargin: 12 readonly property bool barAtBottom: Config.options.waffles.bar.bottom property real ambientShadowWidth: 1 + onFocusCleared: { + if (!root.closeOnFocusLost) return; + root.close() + } + + function grabFocus() { // Doesn't work + item.grabFocus(); + } + function close() { item.close(); } @@ -43,9 +54,7 @@ Loader { id: focusGrab active: true windows: [popupWindow] - onCleared: { - root.close() - } + onCleared: root.focusCleared(); } function close() { @@ -53,6 +62,10 @@ Loader { else closeAnim.start(); } + function grabFocus() { + focusGrab.active = true; // Doesn't work + } + implicitWidth: realContent.implicitWidth + (ambientShadow.border.width * 2) + (root.visualMargin * 2) implicitHeight: realContent.implicitHeight + (ambientShadow.border.width * 2) + (root.visualMargin * 2) diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml index 232f025c3..54286ac88 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml @@ -4,43 +4,30 @@ import qs import qs.services import qs.modules.common import qs.modules.waffle.looks +import qs.modules.waffle.bar.tray -BarButton { +BarIconButton { id: root - visible: Updates.available && Updates.updateAdvised + visible: Updates.updateAdvised || Updates.updateStronglyAdvised padding: 4 + iconName: "arrow-sync" + iconSize: 20 // Needed because the icon appears to have some padding + tooltipText: Translation.tr("Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages").arg(Updates.count) onClicked: { Quickshell.execDetached(["bash", "-c", Config.options.apps.update]); } - contentItem: Item { - anchors.centerIn: parent - implicitWidth: iconContent.implicitWidth - implicitHeight: iconContent.implicitHeight - - FluentIcon { - id: iconContent - anchors.centerIn: parent - icon: "arrow-sync" - - Rectangle { - anchors { - right: parent.right - bottom: parent.bottom - margins: 1 - } - implicitWidth: 8 - implicitHeight: implicitWidth - radius: height / 2 - color: Updates.updateStronglyAdvised ? Looks.colors.warning : Looks.colors.accent - } + overlayingItems: Rectangle { + anchors { + right: parent.right + bottom: parent.bottom + margins: 1 } - } - - BarToolTip { - extraVisibleCondition: root.shouldShowTooltip - text: Translation.tr("Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages").arg(Updates.count) + implicitWidth: 8 + implicitHeight: implicitWidth + radius: height / 2 + color: Updates.updateStronglyAdvised ? Looks.colors.warning : Looks.colors.accent } } diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml index 2ac4dc944..cfbc52779 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml @@ -4,6 +4,7 @@ import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.bar.tasks +import qs.modules.waffle.bar.tray Rectangle { id: root @@ -68,6 +69,7 @@ Rectangle { shown: Config.options.waffles.bar.leftAlignApps sourceComponent: WidgetsButton {} } + Tray {} UpdatesButton {} SystemButton {} TimeButton {} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml index ad42a93fa..f33c6e12a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml @@ -17,10 +17,17 @@ MouseArea { previewPopup.show(appEntry, button); } + Behavior on implicitWidth { + animation: Looks.transition.move.createObject(this) + } + // Apps row RowLayout { id: row - anchors.fill: parent + anchors { + top: parent.top + bottom: parent.bottom + } spacing: 0 Repeater { diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml new file mode 100644 index 000000000..b98d7c475 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml @@ -0,0 +1,59 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Qt.labs.synchronizer +import Quickshell +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.bar + +RowLayout { + id: root + + property bool overflowOpen: false + + Layout.fillHeight: true + spacing: 0 + + BarIconButton { + id: overflowButton + + visible: TrayService.unpinnedItems.length > 0 + checked: root.overflowOpen + + iconName: "chevron-down" + iconMonochrome: true + iconRotation: (Config.options.waffles.bar.bottom ? 180 : 0) + (root.overflowOpen ? 180 : 0) + Behavior on iconRotation { + animation: Looks.transition.rotate.createObject(this) + } + + onClicked: { + root.overflowOpen = !root.overflowOpen; + } + + TrayOverflowMenu { + id: trayOverflowLayout + Synchronizer on active { + property alias source: root.overflowOpen + } + } + + BarToolTip { + extraVisibleCondition: overflowButton.shouldShowTooltip + text: qsTr("Show hidden icons") + } + } + + Repeater { + model: ScriptModel { + values: TrayService.pinnedItems + } + delegate: TrayButton { + required property var modelData + item: modelData + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayButton.qml new file mode 100644 index 000000000..f7fa7bb1f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayButton.qml @@ -0,0 +1,45 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.SystemTray +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.bar + +BarIconButton { + id: root + + required property SystemTrayItem item + property alias menuOpen: menu.visible + readonly property bool barAtBottom: Config.options.waffles.bar.bottom + iconSource: item.icon + + onClicked: { + item.activate(); + } + + altAction: () => { + if (item.hasMenu) menu.open() + } + + // This is lazy, but it's not like tray menus on Windoes are consistent... + // TODO: Figure out how to do cascading menus then use a custom menu + QsMenuAnchor { + id: menu + menu: root.item.menu + anchor { + adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX + item: root + gravity: root.barAtBottom ? Edges.Top : Edges.Bottom + edges: root.barAtBottom ? Edges.Top : Edges.Bottom + } + } + + BarToolTip { + extraVisibleCondition: root.shouldShowTooltip + text: TrayService.getTooltipForItem(root.item) + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayOverflowMenu.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayOverflowMenu.qml new file mode 100644 index 000000000..98f449122 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayOverflowMenu.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks +import qs.modules.waffle.bar + +BarPopup { + id: root + + closeOnFocusLost: false + onFocusCleared: { + print("uwu") + print(contentItem.children) + const hasMenuOpen = contentItem.children.some(c => (c.menuOpen)); + if (!hasMenuOpen) root.close(); + else root.grabFocus(); + } + + contentItem: GridLayout { + id: contentItem + anchors.centerIn: parent + columns: Math.ceil(Math.sqrt(TrayService.unpinnedItems.length)) + columnSpacing: 0 + rowSpacing: 0 + + Repeater { + model: TrayService.unpinnedItems + delegate: TrayButton { + required property var modelData + item: modelData + + topInset: 0 + bottomInset: 0 + implicitWidth: 40 + implicitHeight: 40 + + colBackground: ColorUtils.transparentize(Looks.colors.bg2) + colBackgroundHover: Looks.colors.bg2Hover + colBackgroundActive: Looks.colors.bg2Active + + onMenuOpenChanged: { + // The overflow menu should only be closed when the user clicks outside + // However the focus grab refuses to reactivate, so we can't have that + // But most of the time the user dismisses the menu by clicking outside anyway, + // so this is acceptable. + if (!menuOpen) { + root.close(); + } + } + } + } + } +} + diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index d2f67fea7..c21e15ed1 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -117,6 +117,14 @@ Singleton { } } + property Component rotate: Component { + NumberAnimation { + duration: 170 + easing.type: Easing.BezierSpline + easing.bezierCurve: transition.easing.bezierCurve.easeInOut + } + } + property Component anchor: Component { AnchorAnimation { duration: 160