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 (event.button === Qt.RightButton && modelData.hasMenu) {
|
||||
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
|
||||
contextMenu.menuX = pos.x;
|
||||
contextMenu.dropdownX = pos.x;
|
||||
contextMenu.trayItem = modelData;
|
||||
menuOpener.menu = modelData.menu;
|
||||
contextMenu.visible = true;
|
||||
|
|
@ -731,18 +731,18 @@ in
|
|||
}
|
||||
}
|
||||
|
||||
// Custom-rendered context menu
|
||||
PopupWindow {
|
||||
id: contextMenu
|
||||
property var trayItem: null
|
||||
// Reusable dropdown component
|
||||
component BarDropdown: PopupWindow {
|
||||
id: dropdown
|
||||
property bool open: false
|
||||
property bool justDismissed: false
|
||||
property real menuX: 0
|
||||
property real fullWidth: menuItems.width + 16
|
||||
property real fullHeight: menuItems.height + 12
|
||||
property real dropdownX: 0
|
||||
property real fullWidth: 200
|
||||
property real fullHeight: 200
|
||||
default property alias content: dropdownContent.data
|
||||
|
||||
anchor.window: bar
|
||||
anchor.rect.x: menuX - (fullWidth + 16) / 2
|
||||
anchor.rect.x: dropdownX - (fullWidth + 16) / 2
|
||||
anchor.rect.y: bar.height
|
||||
anchor.edges: Edges.Top | Edges.Left
|
||||
anchor.gravity: Edges.Bottom | Edges.Right
|
||||
|
|
@ -759,29 +759,23 @@ in
|
|||
} else {
|
||||
open = false;
|
||||
justDismissed = true;
|
||||
menuDismissGuard.start();
|
||||
menuOpener.menu = null;
|
||||
_dismissGuard.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: menuDismissGuard
|
||||
id: _dismissGuard
|
||||
interval: 100
|
||||
onTriggered: contextMenu.justDismissed = false
|
||||
onTriggered: dropdown.justDismissed = false
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
}
|
||||
|
||||
// Concave corner — left
|
||||
Item {
|
||||
anchors.right: menuContent.left
|
||||
anchors.right: _dropdownRect.left
|
||||
anchors.top: parent.top
|
||||
width: 8
|
||||
height: Math.min(8, menuContent.height)
|
||||
height: Math.min(8, _dropdownRect.height)
|
||||
clip: true
|
||||
visible: menuContent.height > 0
|
||||
visible: _dropdownRect.height > 0
|
||||
Canvas {
|
||||
anchors.top: parent.top
|
||||
width: 8; height: 8
|
||||
|
|
@ -797,14 +791,13 @@ in
|
|||
}
|
||||
}
|
||||
|
||||
// Concave corner — right
|
||||
Item {
|
||||
anchors.left: menuContent.right
|
||||
anchors.left: _dropdownRect.right
|
||||
anchors.top: parent.top
|
||||
width: 8
|
||||
height: Math.min(8, menuContent.height)
|
||||
height: Math.min(8, _dropdownRect.height)
|
||||
clip: true
|
||||
visible: menuContent.height > 0
|
||||
visible: _dropdownRect.height > 0
|
||||
Canvas {
|
||||
anchors.top: parent.top
|
||||
width: 8; height: 8
|
||||
|
|
@ -821,11 +814,11 @@ in
|
|||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContent
|
||||
id: _dropdownRect
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: contextMenu.fullWidth
|
||||
height: contextMenu.open ? contextMenu.fullHeight : 0
|
||||
width: dropdown.fullWidth
|
||||
height: dropdown.open ? dropdown.fullHeight : 0
|
||||
color: "#D1${c.base00}"
|
||||
radius: 8
|
||||
topLeftRadius: 0
|
||||
|
|
@ -836,66 +829,87 @@ in
|
|||
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuItems
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 6
|
||||
width: 200
|
||||
Item {
|
||||
id: dropdownContent
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
// Context menu
|
||||
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 {
|
||||
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
|
||||
visible: modelData.isSeparator
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 20
|
||||
height: 1
|
||||
color: "#${c.base03}"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.isSeparator
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 20
|
||||
height: 1
|
||||
color: "#${c.base03}"
|
||||
RowLayout {
|
||||
visible: !modelData.isSeparator
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
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 {
|
||||
visible: !modelData.isSeparator
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
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
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: modelData.buttonType !== QsMenuButtonType.None
|
||||
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
|
||||
color: "#${c.base0D}"
|
||||
font.family: "FiraMono Nerd Font"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
Text {
|
||||
visible: modelData.buttonType !== QsMenuButtonType.None
|
||||
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
|
||||
color: "#${c.base0D}"
|
||||
font.family: "FiraMono Nerd Font"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: !modelData.isSeparator && modelData.enabled
|
||||
onClicked: {
|
||||
modelData.triggered();
|
||||
contextMenu.visible = false;
|
||||
}
|
||||
MouseArea {
|
||||
id: itemMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: !modelData.isSeparator && modelData.enabled
|
||||
onClicked: {
|
||||
modelData.triggered();
|
||||
contextMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -904,114 +918,11 @@ in
|
|||
}
|
||||
|
||||
// Calendar popup
|
||||
PopupWindow {
|
||||
BarDropdown {
|
||||
id: calPopup
|
||||
anchor.window: bar
|
||||
anchor.rect.x: bar.width / 2 - (fullWidth + 16) / 2
|
||||
anchor.rect.y: bar.height
|
||||
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 }
|
||||
}
|
||||
dropdownX: bar.width / 2
|
||||
fullWidth: calCol.width + 32
|
||||
fullHeight: calCol.height + 24
|
||||
|
||||
Column {
|
||||
id: calCol
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue