waffles: start menu apps

This commit is contained in:
end-4
2025-12-06 13:17:29 +01:00
parent ed89ad882f
commit 80a7804ade
17 changed files with 822 additions and 186 deletions
@@ -346,6 +346,10 @@ Singleton {
}
}
property JsonObject launcher: JsonObject {
property list<string> pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"]
}
property JsonObject light: JsonObject {
property JsonObject night: JsonObject {
property bool automatic: true
@@ -87,38 +87,16 @@ Item {
}
}
Column {
VerticalPageIndicator {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 6
spacing: 6
NavigationArrow {
down: false
}
Repeater {
model: root.pages
delegate: MouseArea {
id: pageIndicator
required property int index
hoverEnabled: true
onClicked: root.currentPage = index
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: 6
implicitHeight: 6
Circle {
anchors.centerIn: parent
diameter: (index === root.currentPage || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4
color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
}
}
}
NavigationArrow {
down: true
}
currentIndex: root.currentPage
count: root.pages
onClicked: (index) => root.currentPage = index
onIncreasePage: root.increasePage();
onDecreasePage: root.decreasePage();
}
FocusedScrollMouseArea {
@@ -126,25 +104,7 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: false
onScrollUp: decreasePage();
onScrollDown: increasePage();
}
component NavigationArrow: FluentIcon {
id: navArrow
required property bool down
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: 12
implicitWidth: 12 - (2 * upArea.containsPress)
icon: down ? "caret-down" : "caret-up"
color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
filled: true
opacity: ((down && root.currentPage < root.pages - 1) || (!down && root.currentPage > 0)) ? 1 : 0
MouseArea {
id: upArea
anchors.fill: parent
hoverEnabled: true
onClicked: navArrow.down ? root.increasePage() : root.decreasePage();
}
onScrollUp: root.decreasePage();
onScrollDown: root.increasePage();
}
}
@@ -96,7 +96,7 @@ Singleton {
property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency)
property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency)
property color bg1Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base, root.backgroundTransparency)
property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base
property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency)
property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency)
property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency)
@@ -146,7 +146,7 @@ Singleton {
property int thin: Font.Normal
property int regular: Font.Medium
property int strong: Font.DemiBold
property int stronger: Font.Bold
property int stronger: (Font.DemiBold + 2*Font.Bold) / 3
}
property QtObject pixelSize: QtObject {
property real normal: 11
@@ -0,0 +1,73 @@
pragma ComponentBehavior: Bound
import Qt.labs.synchronizer
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
Column {
id: root
property bool showArrows: true
property int currentIndex: 0
property int count: 1
signal clicked(int index)
signal increasePage()
signal decreasePage()
visible: count > 1
spacing: 6
NavigationArrow {
visible: root.showArrows
down: false
}
Repeater {
model: root.count
delegate: MouseArea {
id: pageIndicator
required property int index
hoverEnabled: true
onClicked: root.clicked(index);
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: 6
implicitHeight: 6
Circle {
anchors.centerIn: parent
diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4
color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
}
}
}
NavigationArrow {
visible: root.showArrows
down: true
}
component NavigationArrow: FluentIcon {
id: navArrow
required property bool down
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: 12
implicitWidth: 12 - (2 * upArea.containsPress)
icon: down ? "caret-down" : "caret-up"
color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
filled: true
opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0
MouseArea {
id: upArea
anchors.fill: parent
hoverEnabled: true
onClicked: navArrow.down ? root.increasePage() : root.decreasePage();
}
}
}
@@ -0,0 +1,7 @@
import QtQuick
import qs.services
QtObject {
property string name
property list<string> categories
}
@@ -0,0 +1,84 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
GridLayout {
id: root
columns: 4
Component {
id: aggAppCatComp
AggregatedAppCategoryModel {}
}
property list<AggregatedAppCategoryModel> aggregatedCategories: [
aggAppCatComp.createObject(null, {
name: Translation.tr("Productivity"),
categories: ["Development", "Education", "Network", "Office"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Utilities & Tools"),
categories: ["Utility", "Science"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Creativity"),
categories: ["AudioVideo", "Graphics"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Other"),
categories: ["Game"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("System"),
categories: ["Settings", "System"]
})
]
Repeater {
model: root.aggregatedCategories
delegate: AppCategory {
required property var modelData
aggregatedCategory: modelData
}
}
columnSpacing: 27
rowSpacing: 12
component AppCategory: Item {
id: categoryItem
property AggregatedAppCategoryModel aggregatedCategory
implicitWidth: categoryLayout.implicitWidth
implicitHeight: categoryLayout.implicitHeight
ColumnLayout {
id: categoryLayout
anchors.fill: parent
spacing: 4
AppCategoryGrid {
id: categoryGrid
Layout.fillWidth: true
aggregatedCategory: categoryItem.aggregatedCategory
}
WButton {
id: categoryButton
Layout.fillWidth: true
implicitHeight: 32
contentItem: WText {
id: categoryButtonText
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
text: categoryItem.aggregatedCategory.name
}
onClicked: {
categoryGrid.openCategoryFolder();
}
}
}
}
}
@@ -0,0 +1,311 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
Rectangle {
id: root
property AggregatedAppCategoryModel aggregatedCategory
property list<DesktopEntry> desktopEntries: DesktopEntries.applications.values.filter(app => {
const appCategories = app.categories;
const gridCategories = root.aggregatedCategory.categories;
return appCategories.some(cat => gridCategories.indexOf(cat) !== -1);
})
property Item windowRootItem: {
var item = root;
// print("FINDING ROOT")
while (item.parent != null) {
if (item.parent.toString().includes("ProxyWindow"))
break;
item = item.parent;
}
// print(item.width, item.height)
return item;
}
function openCategoryFolder() {
categoryFolderPopup.open();
}
radius: Looks.radius.large
color: Looks.colors.bg1
border.width: 1
border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7)
implicitWidth: 156
implicitHeight: 156
GridLayout {
id: categoryAppsGrid
anchors.fill: parent
anchors.margins: 10
columns: 2
rows: 2
columnSpacing: 0
rowSpacing: 0
uniformCellHeights: true
uniformCellWidths: true
Repeater {
model: ScriptModel {
values: root.desktopEntries.slice(0, 3)
}
delegate: SmallGridAppButton {
required property DesktopEntry modelData
desktopEntry: modelData
}
}
Loader {
id: categoryOpenButtonLoader
// It's like this on the real thing - you get an invisible button if there's not enough items
opacity: root.desktopEntries.length > 3 ? 1 : 0
active: true
sourceComponent: CategoryOpenButton {
aggregatedCategory: root.aggregatedCategory
}
}
}
Popup {
id: categoryFolderPopup
// I don't even know what the fuck is going on at this point
// I hate point mapping
property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2)
property point windowCenterPoint: {
const rootContentItem = root.windowRootItem;
const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2);
const sectionItem = root.parent.parent.parent;
const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y);
const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y);
return Qt.point(canvasPosInRoot.x, targetY);
}
enter: Transition {
NumberAnimation {
target: categoryFolderPopup
property: "x"
from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 3 / 2
to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
NumberAnimation {
target: categoryFolderPopup
property: "y"
from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height / 2
to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
NumberAnimation {
target: categoryFolderPopup
property: "scale"
from: 0
to: 1
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
exit: Transition {
NumberAnimation {
target: categoryFolderPopup
property: "x"
to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 3 / 2
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
NumberAnimation {
target: categoryFolderPopup
property: "y"
to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height / 2
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
NumberAnimation {
target: categoryFolderPopup
property: "scale"
from: 1
to: 0
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
}
background: null
Loader {
active: categoryFolderPopup.visible
sourceComponent: CategoryFolderContent {
title: root.aggregatedCategory.name
desktopEntries: root.desktopEntries
}
}
}
component CategoryFolderContent: WToolTipContent {
id: categoryFolderContent
property string title
property list<DesktopEntry> desktopEntries: root.desktopEntries
horizontalPadding: 0
verticalPadding: 0
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: 448
implicitHeight: 376
ColumnLayout {
anchors {
fill: parent
leftMargin: 32
rightMargin: 32
topMargin: 40
bottomMargin: 32
}
spacing: 28
WText {
Layout.fillWidth: true
text: categoryFolderContent.title
font.pixelSize: Looks.font.pixelSize.xlarger
font.weight: Looks.font.weight.stronger
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
SwipeView {
id: categoryFolderSwipeView
anchors.fill: parent
orientation: Qt.Vertical
clip: true
Repeater {
model: Math.ceil(root.desktopEntries.length / 12)
delegate: Item {
id: folderPage
required property int index
width: SwipeView.view.width
height: SwipeView.view.height
BigAppGrid {
anchors {
top: parent.top
left: parent.left
}
columns: 4
rows: 3
desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12)
}
}
}
}
VerticalPageIndicator {
anchors.verticalCenter: parent.verticalCenter
anchors.right: categoryFolderSwipeView.right
anchors.rightMargin: -19
showArrows: false
currentIndex: categoryFolderSwipeView.currentIndex
count: Math.ceil(root.desktopEntries.length / 12)
onClicked: index => categoryFolderSwipeView.currentIndex = index
}
}
}
FocusedScrollMouseArea {
z: 999
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: false
onScrollUp: categoryFolderSwipeView.decrementCurrentIndex()
onScrollDown: categoryFolderSwipeView.incrementCurrentIndex()
}
}
}
component CategoryOpenButton: SmallGridButton {
id: categoryOpenButton
property AggregatedAppCategoryModel aggregatedCategory
onClicked: root.openCategoryFolder()
contentItem: Item {
Behavior on scale {
NumberAnimation {
id: scaleAnim
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
GridLayout {
anchors.centerIn: parent
rows: 2
columns: 2
rowSpacing: 2
columnSpacing: 2
Repeater {
model: root.desktopEntries.slice(3, 7)
delegate: WAppIcon {
required property DesktopEntry modelData
tryCustomIcon: false
iconName: modelData.icon
implicitSize: 16
}
}
}
}
}
component SmallGridAppButton: SmallGridButton {
id: smallGridAppButton
property DesktopEntry desktopEntry
onClicked: {
GlobalStates.searchOpen = false;
desktopEntry.execute();
}
contentItem: Item {
Behavior on scale {
NumberAnimation {
id: scaleAnim
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
WAppIcon {
anchors.centerIn: parent
tryCustomIcon: false
iconName: smallGridAppButton.desktopEntry.icon
implicitSize: 34
}
}
WToolTip {
text: smallGridAppButton.desktopEntry.name
}
}
component SmallGridButton: WButton {
id: root
implicitWidth: 68
implicitHeight: 68
property real pressedScale: 5 / 6
onDownChanged: {
contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25
}
}
}
@@ -0,0 +1,39 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
GridLayout {
id: root
property list<var> desktopEntries: []
columnSpacing: 0
rowSpacing: 0
uniformCellHeights: true
uniformCellWidths: true
Repeater {
model: ScriptModel {
values: root.desktopEntries
}
delegate: StartAppButton {
id: pinnedAppButton
required property var modelData
desktopEntry: modelData
onClicked: {
GlobalStates.searchOpen = false;
desktopEntry.execute();
}
}
}
}
@@ -189,6 +189,7 @@ RowLayout {
const isAppEntry = resultPreview.entry.type === Translation.tr("App");
const appId = isAppEntry ? resultPreview.entry.id : "";
const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false;
const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false;
var result = [
searchResultComp.createObject(null, {
name: resultPreview.entry.verb,
@@ -207,6 +208,20 @@ RowLayout {
TaskbarApps.togglePin(appId);
}
})
] : []),
...(isAppEntry ? [
searchResultComp.createObject(null, {
name: startPinned ? Translation.tr("Unpin from start") : Translation.tr("Pin to start"),
iconName: startPinned ? "keep_off" : "keep",
iconType: LauncherSearchResult.IconType.Material,
execute: () => {
if (Config.options.launcher.pinnedApps.indexOf(appId) !== -1) {
Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId)
} else {
Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([appId])
}
}
})
] : [])
];
result = result.concat(resultPreview.entry.actions);
@@ -0,0 +1,46 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WButton {
id: root
required property DesktopEntry desktopEntry
implicitWidth: 96
implicitHeight: 84
horizontalPadding: 0
verticalPadding: 0
contentItem: ColumnLayout {
spacing: 3
WAppIcon {
Layout.topMargin: 12
Layout.alignment: Qt.AlignHCenter
iconName: root.desktopEntry.icon
implicitSize: 34
tryCustomIcon: false
}
WText {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 8
Layout.rightMargin: 8
text: root.desktopEntry.name
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
}
WToolTip {
text: root.desktopEntry.name
}
}
@@ -99,7 +99,7 @@ WBarAttachedPanelContent {
}
}
Item {
implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred
implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred
Layout.fillWidth: true
Loader {
id: pageContentLoader
@@ -0,0 +1,74 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
BodyRectangle {
id: root
ColumnLayout {
anchors {
fill: parent
leftMargin: 32
rightMargin: 32
topMargin: 25
bottomMargin: 30
}
spacing: 26
PinnedApps {
Layout.fillWidth: true
}
AllApps {
implicitHeight: 300 // for now
}
}
component PinnedApps: PageSection {
title: Translation.tr("Pinned")
BigAppGrid {
Layout.fillWidth: true
columns: 8
desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId))
}
}
component AllApps: PageSection {
title: Translation.tr("All")
AllAppsGrid {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 32
Layout.rightMargin: 32
}
}
component PageSection: ColumnLayout {
id: pageSection
required property string title
default property alias data: pageSectionContentArea.data
spacing: 16
WText {
Layout.leftMargin: 32
text: pageSection.title
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.stronger
}
ColumnLayout {
id: pageSectionContentArea
Layout.fillWidth: true
}
}
}
@@ -16,7 +16,7 @@ WPanelPageColumn {
WPanelSeparator {}
BodyRectangle {
StartPageApps {
Layout.fillHeight: true
}
@@ -29,7 +29,7 @@ WPanelPageColumn {
component StartFooter: FooterRectangle {
implicitHeight: 63
UserButton {
StartUserButton {
anchors {
left: parent.left
leftMargin: 52
@@ -48,134 +48,6 @@ WPanelPageColumn {
}
}
component UserButton: WBorderlessButton {
id: userButton
implicitWidth: userButtonRow.implicitWidth + 12 * 2
implicitHeight: 40
contentItem: Item {
RowLayout {
id: userButtonRow
anchors.centerIn: parent
spacing: 12
WUserAvatar {
sourceSize: Qt.size(32, 32)
}
WText {
Layout.alignment: Qt.AlignVCenter
text: SystemInfo.username
}
}
}
onClicked: {
userMenu.open();
}
WToolTip {
text: SystemInfo.username
}
Popup {
id: userMenu
x: -51
y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10
background: null
WToolTipContent {
id: popupContent
horizontalPadding: 10
verticalPadding: 7
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: userMenuContentLayout.implicitWidth
implicitHeight: userMenuContentLayout.implicitHeight
ColumnLayout {
id: userMenuContentLayout
anchors {
fill: parent
leftMargin: popupContent.horizontalPadding
rightMargin: popupContent.horizontalPadding
topMargin: popupContent.verticalPadding
bottomMargin: popupContent.verticalPadding
}
spacing: 5
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 6
FluentIcon {
Layout.alignment: Qt.AlignVCenter
implicitSize: 22
icon: "corporation"
monochrome: false
}
WText {
Layout.alignment: Qt.AlignVCenter
text: "Megahard"
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.strong
}
Item { Layout.fillWidth: true }
WBorderlessButton {
Layout.alignment: Qt.AlignVCenter
implicitHeight: 36
implicitWidth: textItem.implicitWidth + 10 * 2
contentItem: WText {
id: textItem
text: Translation.tr("Sign out")
font.pixelSize: Looks.font.pixelSize.large
}
onClicked: Session.logout()
}
}
Item { // Force min width 360 (using min on the item somehow doesn't work)
implicitWidth: 334
}
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 7
Layout.leftMargin: 6
spacing: 12
WUserAvatar {
sourceSize: Qt.size(58, 58)
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
WText {
text: SystemInfo.username
font.pixelSize: Looks.font.pixelSize.larger
font.weight: Looks.font.weight.strong
}
WText {
color: Looks.colors.fg1
text: Translation.tr("Local account")
}
WText {
color: Looks.colors.accent
text: Translation.tr("Manage my account")
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser])
GlobalStates.searchOpen = false;
}
}
}
}
}
}
}
}
}
}
component PowerButton: WBorderlessButton {
id: powerButton
implicitWidth: 40
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WBorderlessButton {
id: userButton
implicitWidth: userButtonRow.implicitWidth + 12 * 2
implicitHeight: 40
contentItem: Item {
RowLayout {
id: userButtonRow
anchors.centerIn: parent
spacing: 12
WUserAvatar {
sourceSize: Qt.size(32, 32)
}
WText {
Layout.alignment: Qt.AlignVCenter
text: SystemInfo.username
}
}
}
onClicked: {
userMenu.open();
}
WToolTip {
text: SystemInfo.username
}
Popup {
id: userMenu
x: -51
y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10
background: null
WToolTipContent {
id: popupContent
horizontalPadding: 10
verticalPadding: 7
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: userMenuContentLayout.implicitWidth
implicitHeight: userMenuContentLayout.implicitHeight
ColumnLayout {
id: userMenuContentLayout
anchors {
fill: parent
leftMargin: popupContent.horizontalPadding
rightMargin: popupContent.horizontalPadding
topMargin: popupContent.verticalPadding
bottomMargin: popupContent.verticalPadding
}
spacing: 5
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 6
FluentIcon {
Layout.alignment: Qt.AlignVCenter
implicitSize: 22
icon: "corporation"
monochrome: false
}
WText {
Layout.alignment: Qt.AlignVCenter
text: "Megahard"
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.strong
}
Item { Layout.fillWidth: true }
WBorderlessButton {
Layout.alignment: Qt.AlignVCenter
implicitHeight: 36
implicitWidth: textItem.implicitWidth + 10 * 2
contentItem: WText {
id: textItem
text: Translation.tr("Sign out")
font.pixelSize: Looks.font.pixelSize.large
}
onClicked: Session.logout()
}
}
Item { // Force min width 360 (using min on the item somehow doesn't work)
implicitWidth: 334
}
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 7
Layout.leftMargin: 6
spacing: 12
WUserAvatar {
sourceSize: Qt.size(58, 58)
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
WText {
text: SystemInfo.username
font.pixelSize: Looks.font.pixelSize.larger
font.weight: Looks.font.weight.strong
}
WText {
color: Looks.colors.fg1
text: Translation.tr("Local account")
}
WText {
color: Looks.colors.accent
text: Translation.tr("Manage my account")
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser])
GlobalStates.searchOpen = false;
}
}
}
}
}
}
}
}
}
}
@@ -65,8 +65,8 @@ RowLayout {
WMenu {
id: accountsMenu
x: -accountsMenu.implicitWidth + optionsButton.implicitWidth
y: optionsButton.height + 10
x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10
y: optionsButton.height
downDirection: true
Action {
icon.name: "people-settings"
@@ -106,9 +106,9 @@ Singleton {
if (newVolume - lastVolume > maxAllowedIncrease) {
sink.audio.volume = lastVolume;
root.sinkProtectionTriggered(Translation.tr("Illegal increment"));
} else if (Math.round(newVolume * 100) / 100 > maxAllowed || newVolume > root.hardMaxValue) {
} else if (newVolume > maxAllowed || newVolume > root.hardMaxValue) {
root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed"));
sink.audio.volume = maxAllowed;
sink.audio.volume = Math.min(lastVolume, maxAllowed);
}
lastVolume = sink.audio.volume;
}
@@ -20,6 +20,17 @@ Singleton {
}
}
// https://specifications.freedesktop.org/menu/latest/category-registry.html
property list<string> mainRegisteredCategories: ["AudioVideo", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility"]
property list<string> appCategories: DesktopEntries.applications.values.reduce((acc, entry) => {
for (const category of entry.categories) {
if (!acc.includes(category) && mainRegisteredCategories.includes(category)) {
acc.push(category);
}
}
return acc;
}, []).sort()
property var searchActions: [
{
action: "accentcolor",