quickshell: extract BarDropdown reusable component
Inline QML component encapsulating PopupWindow + concave corners + morphing animation + dismiss guard. Calendar and tray context menu both use it now. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0aba95779a
commit
f50e2d875f
1 changed files with 98 additions and 187 deletions
|
|
@ -717,7 +717,7 @@ in
|
||||||
if (contextMenu.justDismissed) return;
|
if (contextMenu.justDismissed) return;
|
||||||
if (event.button === Qt.RightButton && modelData.hasMenu) {
|
if (event.button === Qt.RightButton && modelData.hasMenu) {
|
||||||
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
|
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
|
||||||
contextMenu.menuX = pos.x;
|
contextMenu.dropdownX = pos.x;
|
||||||
contextMenu.trayItem = modelData;
|
contextMenu.trayItem = modelData;
|
||||||
menuOpener.menu = modelData.menu;
|
menuOpener.menu = modelData.menu;
|
||||||
contextMenu.visible = true;
|
contextMenu.visible = true;
|
||||||
|
|
@ -731,18 +731,18 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom-rendered context menu
|
// Reusable dropdown component
|
||||||
PopupWindow {
|
component BarDropdown: PopupWindow {
|
||||||
id: contextMenu
|
id: dropdown
|
||||||
property var trayItem: null
|
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property bool justDismissed: false
|
property bool justDismissed: false
|
||||||
property real menuX: 0
|
property real dropdownX: 0
|
||||||
property real fullWidth: menuItems.width + 16
|
property real fullWidth: 200
|
||||||
property real fullHeight: menuItems.height + 12
|
property real fullHeight: 200
|
||||||
|
default property alias content: dropdownContent.data
|
||||||
|
|
||||||
anchor.window: bar
|
anchor.window: bar
|
||||||
anchor.rect.x: menuX - (fullWidth + 16) / 2
|
anchor.rect.x: dropdownX - (fullWidth + 16) / 2
|
||||||
anchor.rect.y: bar.height
|
anchor.rect.y: bar.height
|
||||||
anchor.edges: Edges.Top | Edges.Left
|
anchor.edges: Edges.Top | Edges.Left
|
||||||
anchor.gravity: Edges.Bottom | Edges.Right
|
anchor.gravity: Edges.Bottom | Edges.Right
|
||||||
|
|
@ -759,29 +759,23 @@ in
|
||||||
} else {
|
} else {
|
||||||
open = false;
|
open = false;
|
||||||
justDismissed = true;
|
justDismissed = true;
|
||||||
menuDismissGuard.start();
|
_dismissGuard.start();
|
||||||
menuOpener.menu = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: menuDismissGuard
|
id: _dismissGuard
|
||||||
interval: 100
|
interval: 100
|
||||||
onTriggered: contextMenu.justDismissed = false
|
onTriggered: dropdown.justDismissed = false
|
||||||
}
|
}
|
||||||
|
|
||||||
QsMenuOpener {
|
|
||||||
id: menuOpener
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concave corner — left
|
|
||||||
Item {
|
Item {
|
||||||
anchors.right: menuContent.left
|
anchors.right: _dropdownRect.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: 8
|
width: 8
|
||||||
height: Math.min(8, menuContent.height)
|
height: Math.min(8, _dropdownRect.height)
|
||||||
clip: true
|
clip: true
|
||||||
visible: menuContent.height > 0
|
visible: _dropdownRect.height > 0
|
||||||
Canvas {
|
Canvas {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: 8; height: 8
|
width: 8; height: 8
|
||||||
|
|
@ -797,14 +791,13 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concave corner — right
|
|
||||||
Item {
|
Item {
|
||||||
anchors.left: menuContent.right
|
anchors.left: _dropdownRect.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: 8
|
width: 8
|
||||||
height: Math.min(8, menuContent.height)
|
height: Math.min(8, _dropdownRect.height)
|
||||||
clip: true
|
clip: true
|
||||||
visible: menuContent.height > 0
|
visible: _dropdownRect.height > 0
|
||||||
Canvas {
|
Canvas {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: 8; height: 8
|
width: 8; height: 8
|
||||||
|
|
@ -821,11 +814,11 @@ in
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: menuContent
|
id: _dropdownRect
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: contextMenu.fullWidth
|
width: dropdown.fullWidth
|
||||||
height: contextMenu.open ? contextMenu.fullHeight : 0
|
height: dropdown.open ? dropdown.fullHeight : 0
|
||||||
color: "#D1${c.base00}"
|
color: "#D1${c.base00}"
|
||||||
radius: 8
|
radius: 8
|
||||||
topLeftRadius: 0
|
topLeftRadius: 0
|
||||||
|
|
@ -836,66 +829,87 @@ in
|
||||||
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
|
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
id: menuItems
|
id: dropdownContent
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.fill: parent
|
||||||
anchors.top: parent.top
|
}
|
||||||
anchors.topMargin: 6
|
}
|
||||||
width: 200
|
}
|
||||||
|
|
||||||
Repeater {
|
// Context menu
|
||||||
model: menuOpener.children
|
BarDropdown {
|
||||||
|
id: contextMenu
|
||||||
|
property var trayItem: null
|
||||||
|
fullWidth: menuItems.width + 16
|
||||||
|
fullHeight: menuItems.height + 12
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) menuOpener.menu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
QsMenuOpener {
|
||||||
|
id: menuOpener
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: menuItems
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 6
|
||||||
|
width: 200
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: menuOpener.children
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
width: 200
|
||||||
|
height: modelData.isSeparator ? 9 : 28
|
||||||
|
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
|
||||||
|
? "#${c.base02}" : "transparent"
|
||||||
|
radius: modelData.isSeparator ? 0 : 4
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
required property var modelData
|
visible: modelData.isSeparator
|
||||||
width: 200
|
anchors.centerIn: parent
|
||||||
height: modelData.isSeparator ? 9 : 28
|
width: parent.width - 20
|
||||||
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
|
height: 1
|
||||||
? "#${c.base02}" : "transparent"
|
color: "#${c.base03}"
|
||||||
radius: modelData.isSeparator ? 0 : 4
|
}
|
||||||
|
|
||||||
Rectangle {
|
RowLayout {
|
||||||
visible: modelData.isSeparator
|
visible: !modelData.isSeparator
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
width: parent.width - 20
|
anchors.leftMargin: 10
|
||||||
height: 1
|
anchors.rightMargin: 10
|
||||||
color: "#${c.base03}"
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: modelData.text ?? ""
|
||||||
|
color: modelData.enabled ? "#${c.base05}" : "#${c.base03}"
|
||||||
|
font.family: "FiraMono Nerd Font"
|
||||||
|
font.pixelSize: 12
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
Text {
|
||||||
visible: !modelData.isSeparator
|
visible: modelData.buttonType !== QsMenuButtonType.None
|
||||||
anchors.fill: parent
|
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
|
||||||
anchors.leftMargin: 10
|
color: "#${c.base0D}"
|
||||||
anchors.rightMargin: 10
|
font.family: "FiraMono Nerd Font"
|
||||||
spacing: 8
|
font.pixelSize: 12
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: modelData.text ?? ""
|
|
||||||
color: modelData.enabled ? "#${c.base05}" : "#${c.base03}"
|
|
||||||
font.family: "FiraMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: modelData.buttonType !== QsMenuButtonType.None
|
|
||||||
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
|
|
||||||
color: "#${c.base0D}"
|
|
||||||
font.family: "FiraMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: itemMouse
|
id: itemMouse
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
enabled: !modelData.isSeparator && modelData.enabled
|
enabled: !modelData.isSeparator && modelData.enabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
modelData.triggered();
|
modelData.triggered();
|
||||||
contextMenu.visible = false;
|
contextMenu.visible = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -904,114 +918,11 @@ in
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calendar popup
|
// Calendar popup
|
||||||
PopupWindow {
|
BarDropdown {
|
||||||
id: calPopup
|
id: calPopup
|
||||||
anchor.window: bar
|
dropdownX: bar.width / 2
|
||||||
anchor.rect.x: bar.width / 2 - (fullWidth + 16) / 2
|
fullWidth: calCol.width + 32
|
||||||
anchor.rect.y: bar.height
|
fullHeight: calCol.height + 24
|
||||||
anchor.edges: Edges.Top | Edges.Left
|
|
||||||
anchor.gravity: Edges.Bottom | Edges.Right
|
|
||||||
anchor.adjustment: PopupAdjustment.Slide
|
|
||||||
grabFocus: true
|
|
||||||
visible: false
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
property bool open: false
|
|
||||||
property bool justDismissed: false
|
|
||||||
property real fullWidth: calCol.width + 32
|
|
||||||
property real fullHeight: calCol.height + 24
|
|
||||||
|
|
||||||
implicitWidth: fullWidth + 16
|
|
||||||
implicitHeight: fullHeight + 4
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
open = true;
|
|
||||||
} else {
|
|
||||||
open = false;
|
|
||||||
justDismissed = true;
|
|
||||||
dismissGuardTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: dismissGuardTimer
|
|
||||||
interval: 100
|
|
||||||
onTriggered: calPopup.justDismissed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concave corner — left
|
|
||||||
Item {
|
|
||||||
anchors.right: calContent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
width: 8
|
|
||||||
height: Math.min(8, calContent.height)
|
|
||||||
clip: true
|
|
||||||
visible: calContent.height > 0
|
|
||||||
Canvas {
|
|
||||||
anchors.top: parent.top
|
|
||||||
width: 8
|
|
||||||
height: 8
|
|
||||||
onPaint: {
|
|
||||||
var ctx = getContext("2d");
|
|
||||||
var r = 8;
|
|
||||||
ctx.clearRect(0, 0, r, r);
|
|
||||||
ctx.fillStyle = "#D1${c.base00}";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, 0);
|
|
||||||
ctx.lineTo(r, 0);
|
|
||||||
ctx.lineTo(r, r);
|
|
||||||
ctx.arc(0, r, r, 0, -Math.PI / 2, true);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concave corner — right
|
|
||||||
Item {
|
|
||||||
anchors.left: calContent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
width: 8
|
|
||||||
height: Math.min(8, calContent.height)
|
|
||||||
clip: true
|
|
||||||
visible: calContent.height > 0
|
|
||||||
Canvas {
|
|
||||||
anchors.top: parent.top
|
|
||||||
width: 8
|
|
||||||
height: 8
|
|
||||||
onPaint: {
|
|
||||||
var ctx = getContext("2d");
|
|
||||||
var r = 8;
|
|
||||||
ctx.clearRect(0, 0, r, r);
|
|
||||||
ctx.fillStyle = "#D1${c.base00}";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, 0);
|
|
||||||
ctx.lineTo(r, 0);
|
|
||||||
ctx.arc(r, r, r, -Math.PI / 2, Math.PI, true);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: calContent
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 0
|
|
||||||
width: calPopup.open ? calPopup.fullWidth : calPopup.fullWidth
|
|
||||||
height: calPopup.open ? calPopup.fullHeight : 0
|
|
||||||
color: "#D1${c.base00}"
|
|
||||||
border.width: 0
|
|
||||||
radius: 8
|
|
||||||
topLeftRadius: 0
|
|
||||||
topRightRadius: 0
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: calCol
|
id: calCol
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue