diff --git a/TODO-appindicators.md b/TODO-appindicators.md deleted file mode 100644 index 73ae76d..0000000 --- a/TODO-appindicators.md +++ /dev/null @@ -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`. diff --git a/modules/Flyout.qml b/modules/Flyout.qml index 4b184a8..770205b 100644 --- a/modules/Flyout.qml +++ b/modules/Flyout.qml @@ -28,22 +28,16 @@ PanelWindow { implicitWidth: label.implicitWidth + 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 { anchors.fill: parent color: M.Theme.base00 opacity: M.Theme.barOpacity - radius: M.Theme.radius - - // Cover the top rounded corners so the flyout - // appears flush / attached to the bar above - Rectangle { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: parent.radius - color: M.Theme.base00 - } + topLeftRadius: 0 + topRightRadius: 0 + bottomLeftRadius: M.Theme.radius + bottomRightRadius: M.Theme.radius } Text { diff --git a/modules/TrayMenu.qml b/modules/TrayMenu.qml index d22ada4..2e5b555 100644 --- a/modules/TrayMenu.qml +++ b/modules/TrayMenu.qml @@ -19,6 +19,7 @@ PanelWindow { signal menuClosed() + visible: true color: "transparent" WlrLayershell.layer: WlrLayer.Overlay @@ -59,9 +60,10 @@ PanelWindow { anchors.fill: parent color: M.Theme.base01 opacity: M.Theme.barOpacity - radius: M.Theme.radius - border.color: M.Theme.base02 - border.width: 1 + topLeftRadius: 0 + topRightRadius: 0 + bottomLeftRadius: M.Theme.radius + bottomRightRadius: M.Theme.radius } StackView {