remove TODO, remove top corner radius for flyout
This commit is contained in:
parent
21c16592dd
commit
ed68b9fd93
3 changed files with 11 additions and 148 deletions
|
|
@ -1,133 +0,0 @@
|
||||||
# TODO: App indicator (system tray) improvements
|
|
||||||
|
|
||||||
## Current state
|
|
||||||
|
|
||||||
`modules/Tray.qml` renders tray icons and handles left/right click, but:
|
|
||||||
|
|
||||||
1. **Right-click context menu is broken** — calls `modelData.display(root.bar, mouse.x, mouse.y)` which is not a valid Quickshell `SystemTrayItem` method and does nothing.
|
|
||||||
2. **No tooltips** — `SystemTrayItem` exposes `tooltipTitle` and `tooltipDescription` but they are not shown.
|
|
||||||
3. **Secondary activation** — right-click currently tries to show a menu; for items without a menu, `secondaryActivate()` should be called instead.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quickshell API reference
|
|
||||||
|
|
||||||
```qml
|
|
||||||
// Key SystemTrayItem properties:
|
|
||||||
modelData.id // string — unique app identifier
|
|
||||||
modelData.icon // url — icon source
|
|
||||||
modelData.title // string — display name
|
|
||||||
modelData.tooltipTitle // string — tooltip heading
|
|
||||||
modelData.tooltipDescription // string — tooltip body
|
|
||||||
modelData.menu // QsMenuHandle | null — context menu (null if app has none)
|
|
||||||
|
|
||||||
// Methods:
|
|
||||||
modelData.activate() // primary action (left click)
|
|
||||||
modelData.secondaryActivate() // secondary action (middle click or right click when no menu)
|
|
||||||
|
|
||||||
// Menu traversal:
|
|
||||||
QsMenuOpener { id: opener; menu: someQsMenuHandle }
|
|
||||||
// opener.children → list of QsMenuEntry
|
|
||||||
// entry.text, entry.icon, entry.enabled, entry.isSeparator
|
|
||||||
// entry.hasChildren → true if entry is a submenu
|
|
||||||
// entry.triggered() → invoke the action
|
|
||||||
// entry as QsMenuHandle → pass back to QsMenuOpener for submenu
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What needs to be done
|
|
||||||
|
|
||||||
### 1. Tooltips (easy)
|
|
||||||
|
|
||||||
Add a `HoverHandler` + flyout to each tray icon delegate.
|
|
||||||
Use `FlyoutState` the same way BarSection does it:
|
|
||||||
|
|
||||||
```qml
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: {
|
|
||||||
const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription]
|
|
||||||
.filter(s => s).join("\n") || iconItem.modelData.title;
|
|
||||||
if (hovered && tip) {
|
|
||||||
M.FlyoutState.text = tip;
|
|
||||||
M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x;
|
|
||||||
M.FlyoutState.visible = true;
|
|
||||||
} else if (!hovered) {
|
|
||||||
M.FlyoutState.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Context menus (harder)
|
|
||||||
|
|
||||||
The challenge: menus need their own window/panel to render in (can't render inside the bar's PanelWindow without clipping). Celestia-shell solves this with a dedicated popout PanelWindow per screen.
|
|
||||||
|
|
||||||
**Approach A — Extend the Flyout system (recommended)**
|
|
||||||
|
|
||||||
Extend `FlyoutState` to optionally carry a `QsMenuHandle` instead of just text. When a tray icon is right-clicked, set `FlyoutState.menuHandle = modelData.menu`. In `Flyout.qml`, detect whether to render the text tooltip or a menu:
|
|
||||||
|
|
||||||
```qml
|
|
||||||
// FlyoutState additions:
|
|
||||||
property var menuHandle: null // QsMenuHandle or null
|
|
||||||
property bool isMenu: false
|
|
||||||
|
|
||||||
// In Flyout.qml: show either the text box or the menu panel
|
|
||||||
Loader {
|
|
||||||
active: M.FlyoutState.isMenu && M.FlyoutState.menuHandle !== null
|
|
||||||
sourceComponent: TrayMenuPanel { handle: M.FlyoutState.menuHandle }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`TrayMenuPanel` would use `QsMenuOpener` to iterate the menu, render items as a `Column` of clickable rows, and handle submenus recursively (push/pop a StackView like celestia-shell does, or use a simple recursive Loader).
|
|
||||||
|
|
||||||
**Approach B — Per-icon popup window**
|
|
||||||
|
|
||||||
Each tray delegate instantiates a small PanelWindow on demand:
|
|
||||||
```qml
|
|
||||||
// Only created on right-click
|
|
||||||
Loader {
|
|
||||||
id: menuLoader
|
|
||||||
active: false
|
|
||||||
sourceComponent: TrayMenuWindow {
|
|
||||||
handle: iconItem.modelData.menu
|
|
||||||
screen: root.bar.screen
|
|
||||||
anchorX: ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (mouse.button === Qt.RightButton && iconItem.modelData.menu)
|
|
||||||
menuLoader.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Simpler isolation but harder to manage dismiss/close (clicking outside).
|
|
||||||
|
|
||||||
### 3. Right-click fallback
|
|
||||||
|
|
||||||
If `modelData.menu === null`, right-click should call `modelData.secondaryActivate()` rather than trying to open a menu.
|
|
||||||
|
|
||||||
```qml
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
modelData.activate();
|
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
|
||||||
if (modelData.menu)
|
|
||||||
// open menu (Approach A or B above)
|
|
||||||
else
|
|
||||||
modelData.secondaryActivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
Celestia-shell implements a full menu renderer at:
|
|
||||||
- `modules/bar/popouts/TrayMenu.qml` — `StackView`-based menu with submenu push/pop, separator support, icon+label rows, back button for submenus
|
|
||||||
- `modules/bar/components/TrayItem.qml` — per-item delegate
|
|
||||||
- The menu is shown via `popouts.currentName = "traymenu{index}"` in the popout panel system
|
|
||||||
|
|
||||||
The key QML types needed: `QsMenuOpener`, `QsMenuHandle`, `QsMenuEntry` — all from `import Quickshell`.
|
|
||||||
|
|
@ -28,22 +28,16 @@ PanelWindow {
|
||||||
implicitWidth: label.implicitWidth + M.Theme.barPadding * 2
|
implicitWidth: label.implicitWidth + M.Theme.barPadding * 2
|
||||||
implicitHeight: label.implicitHeight + M.Theme.barPadding * 2
|
implicitHeight: label.implicitHeight + M.Theme.barPadding * 2
|
||||||
|
|
||||||
// Background matching bar style
|
// Background matching bar style — square top corners so it looks
|
||||||
|
// flush / attached to the bar above, rounded bottom corners only
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: M.Theme.base00
|
color: M.Theme.base00
|
||||||
opacity: M.Theme.barOpacity
|
opacity: M.Theme.barOpacity
|
||||||
radius: M.Theme.radius
|
topLeftRadius: 0
|
||||||
|
topRightRadius: 0
|
||||||
// Cover the top rounded corners so the flyout
|
bottomLeftRadius: M.Theme.radius
|
||||||
// appears flush / attached to the bar above
|
bottomRightRadius: M.Theme.radius
|
||||||
Rectangle {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
height: parent.radius
|
|
||||||
color: M.Theme.base00
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ PanelWindow {
|
||||||
|
|
||||||
signal menuClosed()
|
signal menuClosed()
|
||||||
|
|
||||||
|
visible: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
|
@ -59,9 +60,10 @@ PanelWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: M.Theme.base01
|
color: M.Theme.base01
|
||||||
opacity: M.Theme.barOpacity
|
opacity: M.Theme.barOpacity
|
||||||
radius: M.Theme.radius
|
topLeftRadius: 0
|
||||||
border.color: M.Theme.base02
|
topRightRadius: 0
|
||||||
border.width: 1
|
bottomLeftRadius: M.Theme.radius
|
||||||
|
bottomRightRadius: M.Theme.radius
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue