wbar: add right click menus

This commit is contained in:
end-4
2025-11-15 17:30:51 +01:00
parent 839718cc2b
commit 6ee7212bdc
11 changed files with 334 additions and 104 deletions
@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3.28 2.22a.75.75 0 0 0-1.06 1.06l5.905 5.905L4.81 10.33a1.25 1.25 0 0 0-.476 2.065L7.439 15.5 3 19.94V21h1.06l4.44-4.44 3.105 3.105a1.25 1.25 0 0 0 2.065-.476l1.145-3.313 5.905 5.904a.75.75 0 0 0 1.06-1.06L3.28 2.22Zm10.355 12.476-1.252 3.626-6.705-6.705 3.626-1.252 4.331 4.331Zm6.048-3.876-3.787 1.894 1.118 1.118 3.34-1.67a2.75 2.75 0 0 0 .714-4.404l-4.825-4.826a2.75 2.75 0 0 0-4.405.715l-1.67 3.34 1.118 1.117 1.894-3.787a1.25 1.25 0 0 1 2.002-.325l4.826 4.826a1.25 1.25 0 0 1-.325 2.002Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 622 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m16.242 2.932 4.826 4.826a2.75 2.75 0 0 1-.715 4.404l-4.87 2.435a.75.75 0 0 0-.374.426l-1.44 4.166a1.25 1.25 0 0 1-2.065.476L8.5 16.561 4.06 21H3v-1.06l4.44-4.44-3.105-3.104a1.25 1.25 0 0 1 .476-2.066l4.166-1.44a.75.75 0 0 0 .426-.373l2.435-4.87a2.75 2.75 0 0 1 4.405-.715Zm3.766 5.886-4.826-4.826a1.25 1.25 0 0 0-2.002.325l-2.435 4.871a2.25 2.25 0 0 1-1.278 1.12l-3.789 1.31 6.705 6.704 1.308-3.789a2.25 2.25 0 0 1 1.12-1.277l4.872-2.436a1.25 1.25 0 0 0 .325-2.002Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 594 B

@@ -563,6 +563,10 @@ Singleton {
}
property JsonObject waffles: JsonObject {
// Animations on Windoes are kinda janky. Set the following to
// false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button
property bool smootherAnimations: true
property JsonObject bar: JsonObject {
property bool bottom: true
property bool leftAlignApps: false
@@ -285,4 +285,14 @@ Singleton {
}
return str;
}
function toTitleCase(str) {
// Replace "-" and "_" with space, then capitalize each word
return str.replace(/[-_]/g, " ").replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
);
}
}
@@ -1,111 +1,40 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
Loader {
BarPopup {
id: root
default property var menuData
property var model: [
{iconName: "start-here", text: "Start", action: () => {print("hello")}}
]
padding: 2
property Item anchorItem: parent
property real visualMargin: 12
readonly property bool barAtBottom: Config.options.waffles.bar.bottom
property real ambientShadowWidth: 1
contentItem: ColumnLayout {
anchors.centerIn: parent
spacing: 0
active: false
visible: active
sourceComponent: PopupWindow {
id: popupWindow
visible: true
Component.onCompleted: {
openAnim.start();
}
Repeater {
model: root.model
delegate: WButton {
id: btn
Layout.fillWidth: true
anchor {
adjustment: PopupAdjustment.Slide
item: root.anchorItem
gravity: root.barAtBottom ? Edges.Top : Edges.Bottom
edges: root.barAtBottom ? Edges.Top : Edges.Bottom
}
required property var modelData
icon.name: modelData.iconName ? modelData.iconName : ""
monochromeIcon: modelData.monochromeIcon ?? true
text: modelData.text ? modelData.text : ""
HyprlandFocusGrab {
id: focusGrab
active: true
windows: [popupWindow]
onCleared: {
closeAnim.start();
}
}
implicitWidth: realContent.implicitWidth + (ambientShadow.border.width * 2) + (root.visualMargin * 2)
implicitHeight: realContent.implicitHeight + (ambientShadow.border.width * 2) + (root.visualMargin * 2)
property real sourceEdgeMargin: -implicitHeight
PropertyAnimation {
id: openAnim
target: popupWindow
property: "sourceEdgeMargin"
to: (root.ambientShadowWidth + root.visualMargin)
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
SequentialAnimation {
id: closeAnim
PropertyAnimation {
target: popupWindow
property: "sourceEdgeMargin"
to: -implicitHeight
duration: 150
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
ScriptAction {
script: {
root.active = false;
onClicked: {
if (modelData.action) modelData.action();
root.close();
}
}
}
color: "transparent"
Rectangle {
id: ambientShadow
z: 0
anchors {
fill: realContent
margins: -border.width
}
border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency)
border.width: root.ambientShadowWidth
color: "transparent"
radius: realContent.radius + border.width
}
Rectangle {
id: realContent
z: 1
anchors {
left: parent.left
right: parent.right
top: root.barAtBottom ? undefined : parent.top
bottom: root.barAtBottom ? parent.bottom : undefined
margins: root.ambientShadowWidth + root.visualMargin
// Opening anim
bottomMargin: root.barAtBottom ? popupWindow.sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)
topMargin: root.barAtBottom ? (root.ambientShadowWidth + root.visualMargin) : popupWindow.sourceEdgeMargin
}
color: Looks.colors.bg1
radius: Looks.radius.large
// test
implicitWidth: 300
implicitHeight: 400
Menu {
id: menu
}
}
}
}
@@ -0,0 +1,123 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Hyprland
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
Loader {
id: root
required property var contentItem
property real padding: Looks.radius.large - Looks.radius.medium
property bool noSmoothClosing: !Config.options.waffles.smootherAnimations
property Item anchorItem: parent
property real visualMargin: 12
readonly property bool barAtBottom: Config.options.waffles.bar.bottom
property real ambientShadowWidth: 1
function close() {
item.close();
}
active: false
visible: active
sourceComponent: PopupWindow {
id: popupWindow
visible: true
Component.onCompleted: {
openAnim.start();
}
anchor {
adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX
item: root.anchorItem
gravity: root.barAtBottom ? Edges.Top : Edges.Bottom
edges: root.barAtBottom ? Edges.Top : Edges.Bottom
}
HyprlandFocusGrab {
id: focusGrab
active: true
windows: [popupWindow]
onCleared: {
root.close()
}
}
function close() {
if (root.noSmoothClosing) root.active = false;
else closeAnim.start();
}
implicitWidth: realContent.implicitWidth + (ambientShadow.border.width * 2) + (root.visualMargin * 2)
implicitHeight: realContent.implicitHeight + (ambientShadow.border.width * 2) + (root.visualMargin * 2)
property real sourceEdgeMargin: -implicitHeight
PropertyAnimation {
id: openAnim
target: popupWindow
property: "sourceEdgeMargin"
to: (root.ambientShadowWidth + root.visualMargin)
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
SequentialAnimation {
id: closeAnim
PropertyAnimation {
target: popupWindow
property: "sourceEdgeMargin"
to: -implicitHeight
duration: 150
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
ScriptAction {
script: {
root.active = false;
}
}
}
color: "transparent"
Rectangle {
id: ambientShadow
z: 0
anchors {
fill: realContent
margins: -border.width
}
border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency)
border.width: root.ambientShadowWidth
color: "transparent"
radius: realContent.radius + border.width
}
Rectangle {
id: realContent
z: 1
anchors {
left: parent.left
right: parent.right
top: root.barAtBottom ? undefined : parent.top
bottom: root.barAtBottom ? parent.bottom : undefined
margins: root.ambientShadowWidth + root.visualMargin
// Opening anim
bottomMargin: root.barAtBottom ? popupWindow.sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)
topMargin: root.barAtBottom ? (root.ambientShadowWidth + root.visualMargin) : popupWindow.sourceEdgeMargin
}
color: Looks.colors.bg1
radius: Looks.radius.large
// test
implicitWidth: root.contentItem.implicitWidth + (root.padding * 2)
implicitHeight: root.contentItem.implicitHeight + (root.padding * 2)
children: [root.contentItem]
}
}
}
@@ -1,7 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import Quickshell
import qs
import qs.services
import qs.modules.common
@@ -24,10 +24,43 @@ AppButton {
}
altAction: () => {
contextMenu.active = !contextMenu.active;
contextMenu.active = true;
}
BarMenu {
id: contextMenu
model: [
{
text: Translation.tr("Terminal"),
action: () => {
Quickshell.execDetached(["bash", "-c", Config.options.apps.terminal]);
}
},
{
text: Translation.tr("Task Manager"),
action: () => {
Quickshell.execDetached(["bash", "-c", Config.options.apps.taskManager]);
}
},
{
text: Translation.tr("Settings"),
action: () => {
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath("settings.qml")]);
}
},
{
text: Translation.tr("File Explorer"),
action: () => {
Qt.openUrlExternally(Directories.home);
}
},
{
text: Translation.tr("Search"),
action: () => {
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "overview", "toggle"]);
}
},
]
}
}
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
import qs.modules.waffle.bar
import Quickshell
@@ -16,6 +17,7 @@ AppButton {
property bool hasWindows: appEntry.toplevels.length > 0
signal hoverPreviewRequested()
signal hoverPreviewDismissed()
multiple: appEntry.toplevels.length > 1
checked: active
@@ -43,6 +45,12 @@ AppButton {
}
}
altAction: () => {
root.hoverPreviewDismissed()
root.hoverTimer.stop()
contextMenu.active = true;
}
// Active indicator
Rectangle {
id: activeIndicator
@@ -74,4 +82,37 @@ AppButton {
extraVisibleCondition: root.shouldShowTooltip && !root.hasWindows
text: desktopEntry ? desktopEntry.name : appEntry.appId
}
BarMenu {
id: contextMenu
model: [
{
iconName: root.iconName,
text: root.desktopEntry ? root.desktopEntry.name : StringUtils.toTitleCase(appEntry.appId),
monochromeIcon: false,
action: () => {
if (root.desktopEntry) {
root.desktopEntry.execute()
}
}
},
{
iconName: root.appEntry.pinned ? "pin-off" : "pin",
text: root.appEntry.pinned ? qsTr("Unpin from taskbar") : qsTr("Pin to taskbar"),
action: () => {
TaskbarApps.togglePin(root.appEntry.appId);
}
},
...(root.appEntry.toplevels.length > 0 ? [{
iconName: "dismiss",
text: root.multiple ? qsTr("Close all windows") : qsTr("Close window"),
action: () => {
for (let toplevel of root.appEntry.toplevels) {
toplevel.close();
}
}
}] : []),
]
}
}
@@ -17,10 +17,6 @@ MouseArea {
previewPopup.show(appEntry, button);
}
function showContextMenu(appEntry, button) {
// TODO
}
// Apps row
RowLayout {
id: row
@@ -40,9 +36,8 @@ MouseArea {
onHoverPreviewRequested: {
root.showPreviewPopup(appEntry, this)
}
altAction: () => {
root.showContextMenu(appEntry, this)
onHoverPreviewDismissed: {
previewPopup.close()
}
}
}
@@ -55,5 +50,4 @@ MouseArea {
anchor.window: root.QsWindow.window
}
}
@@ -61,7 +61,7 @@ Singleton {
}
property QtObject pixelSize: QtObject {
property real normal: 11
property real large: 15
property real large: 14
}
}
@@ -0,0 +1,94 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
// Generic button with background
Button {
id: root
property color colBackgroundHover: Looks.colors.bg2Hover
property color colBackgroundActive: Looks.colors.bg2Active
property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)
property alias monochromeIcon: buttonIcon.monochrome
property var altAction: () => {}
property var middleClickAction: () => {}
property real inset: 2
topInset: inset
bottomInset: inset
leftInset: inset
rightInset: inset
horizontalPadding: 10
verticalPadding: 6
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
background: Rectangle {
radius: Looks.radius.medium
color: {
if (root.down) {
return root.colBackgroundActive;
} else if ((root.hovered && !root.down) || root.checked) {
return root.colBackgroundHover;
} else {
return root.colBackground;
}
}
Behavior on color {
animation: Looks.transition.color.createObject(this)
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.MiddleButton
onClicked: (event) => {
if (event.button === Qt.LeftButton) root.clicked();
if (event.button === Qt.RightButton) root.altAction();
if (event.button === Qt.MiddleButton) root.middleClickAction();
}
}
contentItem: Item {
anchors {
fill: parent
margins: root.inset
}
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
RowLayout {
id: contentLayout
anchors {
fill: parent
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
}
spacing: 12
FluentIcon {
id: buttonIcon
monochrome: true
implicitSize: 16
Layout.leftMargin: 6
Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter
visible: root.icon.name !== ""
icon: root.icon.name
}
WText {
Layout.rightMargin: 12
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.text
horizontalAlignment: Text.AlignLeft
font {
pixelSize: Looks.font.pixelSize.large
}
}
}
}
}