diff --git a/src/server/src/qml/launcher-window.cpp b/src/server/src/qml/launcher-window.cpp index e6cf40df6..53b168e13 100644 --- a/src/server/src/qml/launcher-window.cpp +++ b/src/server/src/qml/launcher-window.cpp @@ -1,4 +1,8 @@ #include "launcher-window.hpp" +#include "actions/extension/extension-actions.hpp" +#include "actions/root-search/root-search-actions.hpp" +#include "clipboard-actions.hpp" +#include "extension/extension-command.hpp" #include "launcher-window-platform.hpp" #include "hud-bridge.hpp" #include "keybind-bridge.hpp" @@ -17,7 +21,10 @@ #include "navigation-controller.hpp" #include "overlay-controller/overlay-controller.hpp" #include "extensions/vicinae/bug-report-url.hpp" +#include "qml/raycast-store-detail-host.hpp" +#include "qml/vicinae-store-detail-host.hpp" #include "qml/vicinae-store-view-host.hpp" +#include "root-search/extensions/extension-root-provider.hpp" #include "settings-controller/settings-controller.hpp" #include "services/toast/toast-service.hpp" #include "config/config.hpp" @@ -208,6 +215,7 @@ LauncherWindow::LauncherWindow(ApplicationContext &ctx, QObject *parent) connect(nav, &NavigationController::viewPushed, this, [this](const BaseView *) { m_actionPanel->close(); m_footerPanel->close(); + emit footerStatusChanged(); }); connect(m_footerPanel, &ActionPanelController::openChanged, this, [this]() { @@ -418,6 +426,7 @@ void LauncherWindow::handleCurrentViewChanged() { if (m_atRoot != isRoot) { m_atRoot = isRoot; emit atRootChanged(); + emit footerStatusChanged(); } if (m_overrideWidth != 0 || m_overrideHeight != 0) { @@ -558,7 +567,77 @@ void LauncherWindow::positionOnCursorScreen() { m_window->setY(g.y() + (g.height() - m_window->height()) / 3); } -void LauncherWindow::openFooterMenu() { m_footerPanel->toggle(true); } +bool LauncherWindow::footerStatusClickable() const { + auto *command = m_ctx.navigation->activeCommand(); + + return m_atRoot || command; +} + +void LauncherWindow::openFooterMenu() { + if (m_footerPanel->isOpen()) { + m_footerPanel->close(); + return; + } + + if (m_atRoot) { + buildFooterMenu(); + } else if (!buildCommandFooterMenu()) { + return; + } + + m_footerPanel->toggle(true); +} + +bool LauncherWindow::buildCommandFooterMenu() { + auto *active = m_ctx.navigation->activeCommand(); + if (!active) return false; + + auto id = active->uniqueId(); + auto *manager = m_ctx.services->rootItemManager(); + + auto state = std::make_unique(); + state->setAutoSelectPrimary(false); + state->setTitle(active->repositoryDisplayName()); + state->setId(QStringLiteral("command-footer-menu-%1").arg(active->extensionId())); + + auto *commandSection = state->createSection(QStringLiteral("Command")); + auto metadata = manager->itemMetadata(id); + commandSection->addAction(new OpenItemPreferencesAction(id)); + commandSection->addAction(new SetRootItemAliasAction(id)); + commandSection->addAction(new ToggleItemAsFavorite(id, metadata.favorite)); + commandSection->addAction(new CopyItemDeeplink(id)); + + auto *cmd = dynamic_cast(active); + if (!cmd) { + m_footerPanel->setActions(std::move(state)); + return true; + } + + auto *extensionSection = state->createSection(QStringLiteral("Extension")); + + if (cmd->isVicinae()) { + extensionSection->addAction( + new StaticAction(QStringLiteral("View in Store"), ImageURL::builtin(BuiltinIcon::Cart), + [author = cmd->author(), name = cmd->repositoryName()](ApplicationContext *ctx) { + ctx->navigation->pushView(new VicinaeStoreDetailHost(author, name)); + })); + } else if (cmd->isRaycast()) { + extensionSection->addAction( + new StaticAction(QStringLiteral("View in Store"), ImageURL::builtin(BuiltinIcon::Cart), + [author = cmd->author(), name = cmd->repositoryName()](ApplicationContext *ctx) { + ctx->navigation->pushView(new RaycastStoreDetailHost(author, name)); + })); + } + + extensionSection->addAction( + new CopyToClipboardAction(Clipboard::Text(cmd->path().c_str()), QStringLiteral("Copy Extension Path"))); + + auto *dangerSection = state->createSection(); + dangerSection->addAction(new UninstallExtensionAction(cmd->extensionId())); + + m_footerPanel->setActions(std::move(state)); + return true; +} void LauncherWindow::buildFooterMenu() { auto state = std::make_unique(); diff --git a/src/server/src/qml/launcher-window.hpp b/src/server/src/qml/launcher-window.hpp index ac88ae58d..3e978ebd7 100644 --- a/src/server/src/qml/launcher-window.hpp +++ b/src/server/src/qml/launcher-window.hpp @@ -30,6 +30,7 @@ class LauncherWindow : public QObject { Q_PROPERTY(QObject *commandViewHost READ commandViewHost NOTIFY commandViewHostChanged) Q_PROPERTY(QString navigationTitle READ navigationTitle NOTIFY navigationStatusChanged) Q_PROPERTY(ImageUrl navigationIcon READ navigationIcon NOTIFY navigationStatusChanged) + Q_PROPERTY(bool footerStatusClickable READ footerStatusClickable NOTIFY footerStatusChanged) Q_PROPERTY(bool toastActive READ toastActive NOTIFY toastActiveChanged) Q_PROPERTY(QString toastTitle READ toastTitle NOTIFY toastChanged) Q_PROPERTY(QString toastMessage READ toastMessage NOTIFY toastChanged) @@ -65,6 +66,7 @@ class LauncherWindow : public QObject { QString navigationTitle() const { return m_navigationTitle; } ImageUrl navigationIcon() const { return m_navigationIcon; } + bool footerStatusClickable() const; bool toastActive() const { return m_toastActive; } QString toastTitle() const { return m_toastTitle; } QString toastMessage() const { return m_toastMessage; } @@ -113,6 +115,7 @@ class LauncherWindow : public QObject { void commandViewReplaced(const QUrl &componentUrl, const QVariantMap &properties); void commandViewPopped(); void navigationStatusChanged(); + void footerStatusChanged(); void toastActiveChanged(); void toastChanged(); void isLoadingChanged(); @@ -138,6 +141,7 @@ class LauncherWindow : public QObject { void setExclusiveFocus(bool exclusive); void updateLayerShellProps(); void buildFooterMenu(); + bool buildCommandFooterMenu(); ApplicationContext &m_ctx; ActionPanelController *m_actionPanel; diff --git a/src/server/src/qml/qml/Footer.qml b/src/server/src/qml/qml/Footer.qml index 9f0afeb7f..4549e733d 100644 --- a/src/server/src/qml/qml/Footer.qml +++ b/src/server/src/qml/qml/Footer.qml @@ -14,7 +14,8 @@ Item { FooterNavStatus { visible: !launcher.toastActive - clickable: launcher.atRoot + clickable: launcher.footerStatusClickable + rootMode: launcher.atRoot availableWidth: parent.width anchors.verticalCenter: parent.verticalCenter onClicked: launcher.openFooterMenu() diff --git a/src/server/src/qml/qml/FooterNavStatus.qml b/src/server/src/qml/qml/FooterNavStatus.qml index da5132103..4f0b201b4 100644 --- a/src/server/src/qml/qml/FooterNavStatus.qml +++ b/src/server/src/qml/qml/FooterNavStatus.qml @@ -4,20 +4,23 @@ Item { id: root property bool clickable: false + property bool rootMode: false property real availableWidth: 0 signal clicked - readonly property int buttonSize: 26 + readonly property int buttonSize: 28 + readonly property int backgroundPadding: root.rootMode ? 0 : 8 + readonly property real textMaxWidth: Math.max(0, root.availableWidth - (navIcon.visible ? navIcon.width + row.spacing : 0)) - implicitWidth: clickable ? 20 : row.implicitWidth + implicitWidth: root.rootMode ? 20 : row.implicitWidth implicitHeight: Math.max(row.implicitHeight, clickable ? 26 : 0) Rectangle { visible: root.clickable && (mouseArea.containsMouse || footerPanel.open) - x: -Math.round((root.buttonSize - root.width) / 2) + x: root.rootMode ? -Math.round((root.buttonSize - root.width) / 2) : -root.backgroundPadding anchors.verticalCenter: parent.verticalCenter - width: root.buttonSize + width: root.rootMode ? root.buttonSize : root.width + 2 * root.backgroundPadding height: root.buttonSize radius: 6 color: Theme.listItemHoverBg @@ -33,8 +36,8 @@ Item { id: navIcon width: 20 height: 20 - source: root.clickable ? Img.builtin("vicinae").withFillColor(Theme.textMuted) : launcher.navigationIcon - visible: root.clickable || launcher.navigationIcon.valid + source: root.rootMode ? Img.builtin("vicinae").withFillColor(Theme.textMuted) : launcher.navigationIcon + visible: root.rootMode || launcher.navigationIcon.valid anchors.verticalCenter: parent.verticalCenter } @@ -44,7 +47,7 @@ Item { font.family: Theme.fontFamily font.pointSize: Theme.smallerFontSize elide: Text.ElideRight - width: Math.max(0, root.availableWidth - (navIcon.visible ? navIcon.width + row.spacing : 0)) + width: Math.min(implicitWidth, root.textMaxWidth) anchors.verticalCenter: parent.verticalCenter visible: launcher.navigationTitle !== "" } @@ -52,9 +55,9 @@ Item { MouseArea { id: mouseArea - x: -Math.round((root.buttonSize - root.width) / 2) + x: root.rootMode ? -Math.round((root.buttonSize - root.width) / 2) : -root.backgroundPadding anchors.verticalCenter: parent.verticalCenter - width: root.buttonSize + width: root.rootMode ? root.buttonSize : root.width + 2 * root.backgroundPadding height: root.buttonSize enabled: root.clickable hoverEnabled: enabled